All Files ( 38.02% covered at 28.31 hits/line )
254 files in total.
9554 relevant lines,
3632 lines covered and
5922 lines missed.
(
38.02%
)
-
# frozen_string_literal: true
-
-
#--
-
# Copyright (c) 2005-2020 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
24
require "securerandom"
-
24
require "active_support/dependencies/autoload"
-
24
require "active_support/version"
-
24
require "active_support/logger"
-
24
require "active_support/lazy_load_hooks"
-
24
require "active_support/core_ext/date_and_time/compatibility"
-
-
24
module ActiveSupport
-
24
extend ActiveSupport::Autoload
-
-
24
autoload :Concern
-
24
autoload :ActionableError
-
24
autoload :ConfigurationFile
-
24
autoload :CurrentAttributes
-
24
autoload :Dependencies
-
24
autoload :DescendantsTracker
-
24
autoload :ExecutionWrapper
-
24
autoload :Executor
-
24
autoload :FileUpdateChecker
-
24
autoload :EventedFileUpdateChecker
-
24
autoload :ForkTracker
-
24
autoload :LogSubscriber
-
24
autoload :Notifications
-
24
autoload :Reloader
-
24
autoload :SecureCompareRotator
-
-
24
eager_autoload do
-
24
autoload :BacktraceCleaner
-
24
autoload :ProxyObject
-
24
autoload :Benchmarkable
-
24
autoload :Cache
-
24
autoload :Callbacks
-
24
autoload :Configurable
-
24
autoload :Deprecation
-
24
autoload :Digest
-
24
autoload :Gzip
-
24
autoload :Inflector
-
24
autoload :JSON
-
24
autoload :KeyGenerator
-
24
autoload :MessageEncryptor
-
24
autoload :MessageVerifier
-
24
autoload :Multibyte
-
24
autoload :NumberHelper
-
24
autoload :OptionMerger
-
24
autoload :OrderedHash
-
24
autoload :OrderedOptions
-
24
autoload :StringInquirer
-
24
autoload :EnvironmentInquirer
-
24
autoload :TaggedLogging
-
24
autoload :XmlMini
-
24
autoload :ArrayInquirer
-
end
-
-
24
autoload :Rescuable
-
24
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
-
24
autoload :TestCase
-
-
24
def self.eager_load!
-
super
-
-
NumberHelper.eager_load!
-
end
-
-
24
cattr_accessor :test_order # :nodoc:
-
-
24
def self.to_time_preserves_timezone
-
DateAndTime::Compatibility.preserve_timezone
-
end
-
-
24
def self.to_time_preserves_timezone=(value)
-
23
DateAndTime::Compatibility.preserve_timezone = value
-
end
-
-
24
def self.utc_to_local_returns_utc_offset_times
-
DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times
-
end
-
-
24
def self.utc_to_local_returns_utc_offset_times=(value)
-
DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times = value
-
end
-
end
-
-
24
autoload :I18n, "active_support/i18n"
-
# frozen_string_literal: true
-
-
1
module ActiveSupport
-
# Actionable errors let's you define actions to resolve an error.
-
#
-
# To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
-
# module and invoke the +action+ class macro to define the action. An action
-
# needs a name and a block to execute.
-
1
module ActionableError
-
1
extend Concern
-
-
1
class NonActionable < StandardError; end
-
-
1
included do
-
1
class_attribute :_actions, default: {}
-
end
-
-
1
def self.actions(error) # :nodoc:
-
case error
-
when ActionableError, -> it { Class === it && it < ActionableError }
-
error._actions
-
else
-
{}
-
end
-
end
-
-
1
def self.dispatch(error, name) # :nodoc:
-
actions(error).fetch(name).call
-
rescue KeyError
-
raise NonActionable, "Cannot find action \"#{name}\""
-
end
-
-
1
module ClassMethods
-
# Defines an action that can resolve the error.
-
#
-
# class PendingMigrationError < MigrationError
-
# include ActiveSupport::ActionableError
-
#
-
# action "Run pending migrations" do
-
# ActiveRecord::Tasks::DatabaseTasks.migrate
-
# end
-
# end
-
1
def action(name, &block)
-
2
_actions[name] = block
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support"
-
require "active_support/time"
-
require "active_support/core_ext"
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/symbol/starts_ends_with"
-
-
1
module ActiveSupport
-
# Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
-
# its string-like contents:
-
#
-
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
-
#
-
# variants.phone? # => true
-
# variants.tablet? # => true
-
# variants.desktop? # => false
-
1
class ArrayInquirer < Array
-
# Passes each element of +candidates+ collection to ArrayInquirer collection.
-
# The method returns true if any element from the ArrayInquirer collection
-
# is equal to the stringified or symbolized form of any element in the +candidates+ collection.
-
#
-
# If +candidates+ collection is not given, method returns true.
-
#
-
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
-
#
-
# variants.any? # => true
-
# variants.any?(:phone, :tablet) # => true
-
# variants.any?('phone', 'desktop') # => true
-
# variants.any?(:desktop, :watch) # => false
-
1
def any?(*candidates)
-
if candidates.none?
-
super
-
else
-
candidates.any? do |candidate|
-
include?(candidate.to_sym) || include?(candidate.to_s)
-
end
-
end
-
end
-
-
1
private
-
1
def respond_to_missing?(name, include_private = false)
-
name.end_with?("?") || super
-
end
-
-
1
def method_missing(name, *args)
-
if name.end_with?("?")
-
any?(name[0..-2])
-
else
-
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
# Backtraces often include many lines that are not relevant for the context
-
# under review. This makes it hard to find the signal amongst the backtrace
-
# noise, and adds debugging time. With a BacktraceCleaner, filters and
-
# silencers are used to remove the noisy lines, so that only the most relevant
-
# lines remain.
-
#
-
# Filters are used to modify lines of data, while silencers are used to remove
-
# lines entirely. The typical filter use case is to remove lengthy path
-
# information from the start of each line, and view file paths relevant to the
-
# app directory instead of the file system root. The typical silencer use case
-
# is to exclude the output of a noisy library from the backtrace, so that you
-
# can focus on the rest.
-
#
-
# bc = ActiveSupport::BacktraceCleaner.new
-
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
-
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
-
# bc.clean(exception.backtrace) # perform the cleanup
-
#
-
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
-
# and show as much data as possible, you can always call
-
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
-
# backtrace to a pristine state. If you need to reconfigure an existing
-
# BacktraceCleaner so that it does not filter or modify the paths of any lines
-
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
-
# These two methods will give you a completely untouched backtrace.
-
#
-
# Inspired by the Quiet Backtrace gem by thoughtbot.
-
class BacktraceCleaner
-
def initialize
-
@filters, @silencers = [], []
-
add_gem_filter
-
add_gem_silencer
-
add_stdlib_silencer
-
end
-
-
# Returns the backtrace after all filters and silencers have been run
-
# against it. Filters run first, then silencers.
-
def clean(backtrace, kind = :silent)
-
filtered = filter_backtrace(backtrace)
-
-
case kind
-
when :silent
-
silence(filtered)
-
when :noise
-
noise(filtered)
-
else
-
filtered
-
end
-
end
-
alias :filter :clean
-
-
# Adds a filter from the block provided. Each line in the backtrace will be
-
# mapped against this filter.
-
#
-
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
-
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
-
def add_filter(&block)
-
@filters << block
-
end
-
-
# Adds a silencer from the block provided. If the silencer returns +true+
-
# for a given line, it will be excluded from the clean backtrace.
-
#
-
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
-
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
-
def add_silencer(&block)
-
@silencers << block
-
end
-
-
# Removes all silencers, but leaves in the filters. Useful if your
-
# context of debugging suddenly expands as you suspect a bug in one of
-
# the libraries you use.
-
def remove_silencers!
-
@silencers = []
-
end
-
-
# Removes all filters, but leaves in the silencers. Useful if you suddenly
-
# need to see entire filepaths in the backtrace that you had already
-
# filtered out.
-
def remove_filters!
-
@filters = []
-
end
-
-
private
-
FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
-
-
def add_gem_filter
-
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
-
return if gems_paths.empty?
-
-
gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
-
gems_result = '\3 (\4) \5'
-
add_filter { |line| line.sub(gems_regexp, gems_result) }
-
end
-
-
def add_gem_silencer
-
add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
-
end
-
-
def add_stdlib_silencer
-
add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
-
end
-
-
# Process +ary+ via +filters+ using +method+, ensuring
-
# _something_ gets returned.
-
def process_collection(ary, filters, method)
-
filters.reduce(ary) { |bt, f| bt.send(method) { |line| f.call(line) } }
-
end
-
-
# Use @filters to transform the backtrace via map
-
def filter_backtrace(backtrace)
-
process_collection backtrace, @filters, :map
-
end
-
-
# Use @silencers to reject parts of the backtrace. Guarantee
-
# something non-empty is returned.
-
def silence(backtrace)
-
result = process_collection backtrace, @silencers, :reject
-
result.first ? result : backtrace.dup
-
end
-
-
# Use @silencers to select parts of the backtrace. Guarantee
-
# something non-empty is returned.
-
def noise(backtrace)
-
result = backtrace.select { |line| @silencers.any? { |s| s.call(line) } }
-
result.first ? result : backtrace.dup
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/benchmark"
-
2
require "active_support/core_ext/hash/keys"
-
-
2
module ActiveSupport
-
2
module Benchmarkable
-
# Allows you to measure the execution time of a block in a template and
-
# records the result to the log. Wrap this block around expensive operations
-
# or possible bottlenecks to get a time reading for the operation. For
-
# example, let's say you thought your file processing method was taking too
-
# long; you could wrap it in a benchmark block.
-
#
-
# <% benchmark 'Process data files' do %>
-
# <%= expensive_files_operation %>
-
# <% end %>
-
#
-
# That would add something like "Process data files (345.2ms)" to the log,
-
# which you can then use to compare timings when optimizing your code.
-
#
-
# You may give an optional logger level (<tt>:debug</tt>, <tt>:info</tt>,
-
# <tt>:warn</tt>, <tt>:error</tt>) as the <tt>:level</tt> option. The
-
# default logger level value is <tt>:info</tt>.
-
#
-
# <% benchmark 'Low-level files', level: :debug do %>
-
# <%= lowlevel_files_operation %>
-
# <% end %>
-
#
-
# Finally, you can pass true as the third argument to silence all log
-
# activity (other than the timing information) from inside the block. This
-
# is great for boiling down a noisy block to just a single statement that
-
# produces one log line:
-
#
-
# <% benchmark 'Process data files', level: :info, silence: true do %>
-
# <%= expensive_and_chatty_files_operation %>
-
# <% end %>
-
2
def benchmark(message = "Benchmarking", options = {})
-
if logger
-
options.assert_valid_keys(:level, :silence)
-
options[:level] ||= :info
-
-
result = nil
-
ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
-
logger.send(options[:level], "%s (%.1fms)" % [ message, ms ])
-
result
-
else
-
yield
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
begin
-
1
require "builder"
-
rescue LoadError => e
-
$stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
# frozen_string_literal: true
-
-
13
require "zlib"
-
13
require "active_support/core_ext/array/extract_options"
-
13
require "active_support/core_ext/array/wrap"
-
13
require "active_support/core_ext/enumerable"
-
13
require "active_support/core_ext/module/attribute_accessors"
-
13
require "active_support/core_ext/numeric/bytes"
-
13
require "active_support/core_ext/numeric/time"
-
13
require "active_support/core_ext/object/to_param"
-
13
require "active_support/core_ext/object/try"
-
13
require "active_support/core_ext/string/inflections"
-
-
13
module ActiveSupport
-
# See ActiveSupport::Cache::Store for documentation.
-
13
module Cache
-
13
autoload :FileStore, "active_support/cache/file_store"
-
13
autoload :MemoryStore, "active_support/cache/memory_store"
-
13
autoload :MemCacheStore, "active_support/cache/mem_cache_store"
-
13
autoload :NullStore, "active_support/cache/null_store"
-
13
autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
-
-
# These options mean something to all cache implementations. Individual cache
-
# implementations may support additional options.
-
13
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
-
-
13
module Strategy
-
13
autoload :LocalCache, "active_support/cache/strategy/local_cache"
-
end
-
-
13
class << self
-
# Creates a new Store object according to the given options.
-
#
-
# If no arguments are passed to this method, then a new
-
# ActiveSupport::Cache::MemoryStore object will be returned.
-
#
-
# If you pass a Symbol as the first argument, then a corresponding cache
-
# store class under the ActiveSupport::Cache namespace will be created.
-
# For example:
-
#
-
# ActiveSupport::Cache.lookup_store(:memory_store)
-
# # => returns a new ActiveSupport::Cache::MemoryStore object
-
#
-
# ActiveSupport::Cache.lookup_store(:mem_cache_store)
-
# # => returns a new ActiveSupport::Cache::MemCacheStore object
-
#
-
# Any additional arguments will be passed to the corresponding cache store
-
# class's constructor:
-
#
-
# ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
-
# # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
-
#
-
# If the first argument is not a Symbol, then it will simply be returned:
-
#
-
# ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
-
# # => returns MyOwnCacheStore.new
-
13
def lookup_store(store = nil, *parameters)
-
case store
-
when Symbol
-
options = parameters.extract_options!
-
retrieve_store_class(store).new(*parameters, **options)
-
when Array
-
lookup_store(*store)
-
when nil
-
ActiveSupport::Cache::MemoryStore.new
-
else
-
store
-
end
-
end
-
-
# Expands out the +key+ argument into a key that can be used for the
-
# cache store. Optionally accepts a namespace, and all keys will be
-
# scoped within that namespace.
-
#
-
# If the +key+ argument provided is an array, or responds to +to_a+, then
-
# each of elements in the array will be turned into parameters/keys and
-
# concatenated into a single key. For example:
-
#
-
# ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar"
-
# ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
-
#
-
# The +key+ argument can also respond to +cache_key+ or +to_param+.
-
13
def expand_cache_key(key, namespace = nil)
-
expanded_cache_key = namespace ? +"#{namespace}/" : +""
-
-
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
-
expanded_cache_key << "#{prefix}/"
-
end
-
-
expanded_cache_key << retrieve_cache_key(key)
-
expanded_cache_key
-
end
-
-
13
private
-
13
def retrieve_cache_key(key)
-
case
-
when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
-
when key.respond_to?(:cache_key) then key.cache_key
-
when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
-
when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
-
else key.to_param
-
end.to_s
-
end
-
-
# Obtains the specified cache store class, given the name of the +store+.
-
# Raises an error when the store class cannot be found.
-
13
def retrieve_store_class(store)
-
# require_relative cannot be used here because the class might be
-
# provided by another gem, like redis-activesupport for example.
-
require "active_support/cache/#{store}"
-
rescue LoadError => e
-
raise "Could not find cache store adapter for #{store} (#{e})"
-
else
-
ActiveSupport::Cache.const_get(store.to_s.camelize)
-
end
-
end
-
-
# An abstract cache store class. There are multiple cache store
-
# implementations, each having its own additional features. See the classes
-
# under the ActiveSupport::Cache module, e.g.
-
# ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
-
# popular cache store for large production websites.
-
#
-
# Some implementations may not support all methods beyond the basic cache
-
# methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
-
#
-
# ActiveSupport::Cache::Store can store any serializable Ruby object.
-
#
-
# cache = ActiveSupport::Cache::MemoryStore.new
-
#
-
# cache.read('city') # => nil
-
# cache.write('city', "Duckburgh")
-
# cache.read('city') # => "Duckburgh"
-
#
-
# Keys are always translated into Strings and are case sensitive. When an
-
# object is specified as a key and has a +cache_key+ method defined, this
-
# method will be called to define the key. Otherwise, the +to_param+
-
# method will be called. Hashes and Arrays can also be used as keys. The
-
# elements will be delimited by slashes, and the elements within a Hash
-
# will be sorted by key so they are consistent.
-
#
-
# cache.read('city') == cache.read(:city) # => true
-
#
-
# Nil values can be cached.
-
#
-
# If your cache is on a shared infrastructure, you can define a namespace
-
# for your cache entries. If a namespace is defined, it will be prefixed on
-
# to every key. The namespace can be either a static value or a Proc. If it
-
# is a Proc, it will be invoked when each key is evaluated so that you can
-
# use application logic to invalidate keys.
-
#
-
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
-
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
-
#
-
# Cached data larger than 1kB are compressed by default. To turn off
-
# compression, pass <tt>compress: false</tt> to the initializer or to
-
# individual +fetch+ or +write+ method calls. The 1kB compression
-
# threshold is configurable with the <tt>:compress_threshold</tt> option,
-
# specified in bytes.
-
13
class Store
-
13
cattr_accessor :logger, instance_writer: true
-
-
13
attr_reader :silence, :options
-
13
alias :silence? :silence
-
-
13
class << self
-
13
private
-
13
def retrieve_pool_options(options)
-
{}.tap do |pool_options|
-
pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
-
pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
-
end
-
end
-
-
13
def ensure_connection_pool_added!
-
require "connection_pool"
-
rescue LoadError => e
-
$stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
end
-
-
# Creates a new cache. The options will be passed to any write method calls
-
# except for <tt>:namespace</tt> which can be used to set the global
-
# namespace for the cache.
-
13
def initialize(options = nil)
-
@options = options ? options.dup : {}
-
end
-
-
# Silences the logger.
-
13
def silence!
-
@silence = true
-
self
-
end
-
-
# Silences the logger within a block.
-
13
def mute
-
previous_silence, @silence = defined?(@silence) && @silence, true
-
yield
-
ensure
-
@silence = previous_silence
-
end
-
-
# Fetches data from the cache, using the given key. If there is data in
-
# the cache with the given key, then that data is returned.
-
#
-
# If there is no such data in the cache (a cache miss), then +nil+ will be
-
# returned. However, if a block has been passed, that block will be passed
-
# the key and executed in the event of a cache miss. The return value of the
-
# block will be written to the cache under the given cache key, and that
-
# return value will be returned.
-
#
-
# cache.write('today', 'Monday')
-
# cache.fetch('today') # => "Monday"
-
#
-
# cache.fetch('city') # => nil
-
# cache.fetch('city') do
-
# 'Duckburgh'
-
# end
-
# cache.fetch('city') # => "Duckburgh"
-
#
-
# You may also specify additional options via the +options+ argument.
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
-
# the cache value as missing even if it's present. Passing a block is
-
# required when +force+ is true so this always results in a cache write.
-
#
-
# cache.write('today', 'Monday')
-
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
-
# cache.fetch('today', force: true) # => ArgumentError
-
#
-
# The +:force+ option is useful when you're calling some other method to
-
# ask whether you should force a cache write. Otherwise, it's clearer to
-
# just call <tt>Cache#write</tt>.
-
#
-
# Setting <tt>skip_nil: true</tt> will not cache nil result:
-
#
-
# cache.fetch('foo') { nil }
-
# cache.fetch('bar', skip_nil: true) { nil }
-
# cache.exist?('foo') # => true
-
# cache.exist?('bar') # => false
-
#
-
#
-
# Setting <tt>compress: false</tt> disables compression of the cache entry.
-
#
-
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
-
# All caches support auto-expiring content after a specified number of
-
# seconds. This value can be specified as an option to the constructor
-
# (in which case all entries will be affected), or it can be supplied to
-
# the +fetch+ or +write+ method to effect just one entry.
-
#
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
-
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
-
#
-
# Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
-
# is of the same version. nil is returned on mismatches despite contents.
-
# This feature is used to support recyclable cache keys.
-
#
-
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
-
# a cache entry is used very frequently and is under heavy load. If a
-
# cache expires and due to heavy load several different processes will try
-
# to read data natively and then they all will try to write to cache. To
-
# avoid that case the first process to find an expired cache entry will
-
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
-
# Yes, this process is extending the time for a stale value by another few
-
# seconds. Because of extended life of the previous cache, other processes
-
# will continue to use slightly stale data for a just a bit longer. In the
-
# meantime that first process will go ahead and will write into cache the
-
# new value. After that all the processes will start getting the new value.
-
# The key is to keep <tt>:race_condition_ttl</tt> small.
-
#
-
# If the process regenerating the entry errors out, the entry will be
-
# regenerated after the specified number of seconds. Also note that the
-
# life of stale cache is extended only if it expired recently. Otherwise
-
# a new value is generated and <tt>:race_condition_ttl</tt> does not play
-
# any role.
-
#
-
# # Set all values to expire after one minute.
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
-
#
-
# cache.write('foo', 'original value')
-
# val_1 = nil
-
# val_2 = nil
-
# sleep 60
-
#
-
# Thread.new do
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
-
# sleep 1
-
# 'new value 1'
-
# end
-
# end
-
#
-
# Thread.new do
-
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
-
# 'new value 2'
-
# end
-
# end
-
#
-
# cache.fetch('foo') # => "original value"
-
# sleep 10 # First thread extended the life of cache by another 10 seconds
-
# cache.fetch('foo') # => "new value 1"
-
# val_1 # => "new value 1"
-
# val_2 # => "original value"
-
#
-
# Other options will be handled by the specific cache store implementation.
-
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
-
# miss. +options+ will be passed to the #read and #write calls.
-
#
-
# For example, MemCacheStore's #write method supports the +:raw+
-
# option, which tells the memcached server to store all values as strings.
-
# We can use this option with #fetch too:
-
#
-
# cache = ActiveSupport::Cache::MemCacheStore.new
-
# cache.fetch("foo", force: true, raw: true) do
-
# :bar
-
# end
-
# cache.fetch('foo') # => "bar"
-
13
def fetch(name, options = nil, &block)
-
if block_given?
-
options = merged_options(options)
-
key = normalize_key(name, options)
-
-
entry = nil
-
instrument(:read, name, options) do |payload|
-
cached_entry = read_entry(key, **options) unless options[:force]
-
entry = handle_expired_entry(cached_entry, key, options)
-
entry = nil if entry && entry.mismatched?(normalize_version(name, options))
-
payload[:super_operation] = :fetch if payload
-
payload[:hit] = !!entry if payload
-
end
-
-
if entry
-
get_entry_value(entry, name, options)
-
else
-
save_block_result_to_cache(name, options, &block)
-
end
-
elsif options && options[:force]
-
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
-
else
-
read(name, options)
-
end
-
end
-
-
# Reads data from the cache, using the given key. If there is data in
-
# the cache with the given key, then that data is returned. Otherwise,
-
# +nil+ is returned.
-
#
-
# Note, if data was written with the <tt>:expires_in</tt> or
-
# <tt>:version</tt> options, both of these conditions are applied before
-
# the data is returned.
-
#
-
# Options are passed to the underlying cache implementation.
-
13
def read(name, options = nil)
-
options = merged_options(options)
-
key = normalize_key(name, options)
-
version = normalize_version(name, options)
-
-
instrument(:read, name, options) do |payload|
-
entry = read_entry(key, **options)
-
-
if entry
-
if entry.expired?
-
delete_entry(key, **options)
-
payload[:hit] = false if payload
-
nil
-
elsif entry.mismatched?(version)
-
payload[:hit] = false if payload
-
nil
-
else
-
payload[:hit] = true if payload
-
entry.value
-
end
-
else
-
payload[:hit] = false if payload
-
nil
-
end
-
end
-
end
-
-
# Reads multiple values at once from the cache. Options can be passed
-
# in the last argument.
-
#
-
# Some cache implementation may optimize this method.
-
#
-
# Returns a hash mapping the names provided to the values found.
-
13
def read_multi(*names)
-
options = names.extract_options!
-
options = merged_options(options)
-
-
instrument :read_multi, names, options do |payload|
-
read_multi_entries(names, **options).tap do |results|
-
payload[:hits] = results.keys
-
end
-
end
-
end
-
-
# Cache Storage API to write multiple values at once.
-
13
def write_multi(hash, options = nil)
-
options = merged_options(options)
-
-
instrument :write_multi, hash, options do |payload|
-
entries = hash.each_with_object({}) do |(name, value), memo|
-
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
-
end
-
-
write_multi_entries entries, **options
-
end
-
end
-
-
# Fetches data from the cache, using the given keys. If there is data in
-
# the cache with the given keys, then that data is returned. Otherwise,
-
# the supplied block is called for each key for which there was no data,
-
# and the result will be written to the cache and returned.
-
# Therefore, you need to pass a block that returns the data to be written
-
# to the cache. If you do not want to write the cache when the cache is
-
# not found, use #read_multi.
-
#
-
# Returns a hash with the data for each of the names. For example:
-
#
-
# cache.write("bim", "bam")
-
# cache.fetch_multi("bim", "unknown_key") do |key|
-
# "Fallback value for key: #{key}"
-
# end
-
# # => { "bim" => "bam",
-
# # "unknown_key" => "Fallback value for key: unknown_key" }
-
#
-
# Options are passed to the underlying cache implementation. For example:
-
#
-
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
-
# "buzz"
-
# end
-
# # => {"fizz"=>"buzz"}
-
# cache.read("fizz")
-
# # => "buzz"
-
# sleep(6)
-
# cache.read("fizz")
-
# # => nil
-
13
def fetch_multi(*names)
-
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
-
-
options = names.extract_options!
-
options = merged_options(options)
-
-
instrument :read_multi, names, options do |payload|
-
reads = read_multi_entries(names, **options)
-
writes = {}
-
ordered = names.index_with do |name|
-
reads.fetch(name) { writes[name] = yield(name) }
-
end
-
-
payload[:hits] = reads.keys
-
payload[:super_operation] = :fetch_multi
-
-
write_multi(writes, options)
-
-
ordered
-
end
-
end
-
-
# Writes the value to the cache, with the key.
-
#
-
# Options are passed to the underlying cache implementation.
-
13
def write(name, value, options = nil)
-
options = merged_options(options)
-
-
instrument(:write, name, options) do
-
entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
-
write_entry(normalize_key(name, options), entry, **options)
-
end
-
end
-
-
# Deletes an entry in the cache. Returns +true+ if an entry is deleted.
-
#
-
# Options are passed to the underlying cache implementation.
-
13
def delete(name, options = nil)
-
options = merged_options(options)
-
-
instrument(:delete, name) do
-
delete_entry(normalize_key(name, options), **options)
-
end
-
end
-
-
# Deletes multiple entries in the cache.
-
#
-
# Options are passed to the underlying cache implementation.
-
13
def delete_multi(names, options = nil)
-
options = merged_options(options)
-
names.map! { |key| normalize_key(key, options) }
-
-
instrument :delete_multi, names do
-
delete_multi_entries(names, **options)
-
end
-
end
-
-
# Returns +true+ if the cache contains an entry for the given key.
-
#
-
# Options are passed to the underlying cache implementation.
-
13
def exist?(name, options = nil)
-
options = merged_options(options)
-
-
instrument(:exist?, name) do
-
entry = read_entry(normalize_key(name, options), **options)
-
(entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
-
end
-
end
-
-
# Deletes all entries with keys matching the pattern.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# Some implementations may not support this method.
-
13
def delete_matched(matcher, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
-
end
-
-
# Increments an integer value in the cache.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# Some implementations may not support this method.
-
13
def increment(name, amount = 1, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support increment")
-
end
-
-
# Decrements an integer value in the cache.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# Some implementations may not support this method.
-
13
def decrement(name, amount = 1, options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support decrement")
-
end
-
-
# Cleanups the cache by removing expired entries.
-
#
-
# Options are passed to the underlying cache implementation.
-
#
-
# Some implementations may not support this method.
-
13
def cleanup(options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
-
end
-
-
# Clears the entire cache. Be careful with this method since it could
-
# affect other processes if shared cache is being used.
-
#
-
# The options hash is passed to the underlying cache implementation.
-
#
-
# Some implementations may not support this method.
-
13
def clear(options = nil)
-
raise NotImplementedError.new("#{self.class.name} does not support clear")
-
end
-
-
13
private
-
# Adds the namespace defined in the options to a pattern designed to
-
# match keys. Implementations that support delete_matched should call
-
# this method to translate a pattern that matches names into one that
-
# matches namespaced keys.
-
13
def key_matcher(pattern, options) # :doc:
-
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
-
if prefix
-
source = pattern.source
-
if source.start_with?("^")
-
source = source[1, source.length]
-
else
-
source = ".*#{source[0, source.length]}"
-
end
-
Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
-
else
-
pattern
-
end
-
end
-
-
# Reads an entry from the cache implementation. Subclasses must implement
-
# this method.
-
13
def read_entry(key, **options)
-
raise NotImplementedError.new
-
end
-
-
# Writes an entry to the cache implementation. Subclasses must implement
-
# this method.
-
13
def write_entry(key, entry, **options)
-
raise NotImplementedError.new
-
end
-
-
# Reads multiple entries from the cache implementation. Subclasses MAY
-
# implement this method.
-
13
def read_multi_entries(names, **options)
-
names.each_with_object({}) do |name, results|
-
key = normalize_key(name, options)
-
entry = read_entry(key, **options)
-
-
next unless entry
-
-
version = normalize_version(name, options)
-
-
if entry.expired?
-
delete_entry(key, **options)
-
elsif !entry.mismatched?(version)
-
results[name] = entry.value
-
end
-
end
-
end
-
-
# Writes multiple entries to the cache implementation. Subclasses MAY
-
# implement this method.
-
13
def write_multi_entries(hash, **options)
-
hash.each do |key, entry|
-
write_entry key, entry, **options
-
end
-
end
-
-
# Deletes an entry from the cache implementation. Subclasses must
-
# implement this method.
-
13
def delete_entry(key, **options)
-
raise NotImplementedError.new
-
end
-
-
# Deletes multiples entries in the cache implementation. Subclasses MAY
-
# implement this method.
-
13
def delete_multi_entries(entries, **options)
-
entries.inject(0) do |sum, key|
-
if delete_entry(key, **options)
-
sum + 1
-
else
-
sum
-
end
-
end
-
end
-
-
# Merges the default options with ones specific to a method call.
-
13
def merged_options(call_options)
-
if call_options
-
if options.empty?
-
call_options
-
else
-
options.merge(call_options)
-
end
-
else
-
options
-
end
-
end
-
-
# Expands and namespaces the cache key. May be overridden by
-
# cache stores to do additional normalization.
-
13
def normalize_key(key, options = nil)
-
namespace_key expanded_key(key), options
-
end
-
-
# Prefix the key with a namespace string:
-
#
-
# namespace_key 'foo', namespace: 'cache'
-
# # => 'cache:foo'
-
#
-
# With a namespace block:
-
#
-
# namespace_key 'foo', namespace: -> { 'cache' }
-
# # => 'cache:foo'
-
13
def namespace_key(key, options = nil)
-
options = merged_options(options)
-
namespace = options[:namespace]
-
-
if namespace.respond_to?(:call)
-
namespace = namespace.call
-
end
-
-
if key && key.encoding != Encoding::UTF_8
-
key = key.dup.force_encoding(Encoding::UTF_8)
-
end
-
-
if namespace
-
"#{namespace}:#{key}"
-
else
-
key
-
end
-
end
-
-
# Expands key to be a consistent string value. Invokes +cache_key+ if
-
# object responds to +cache_key+. Otherwise, +to_param+ method will be
-
# called. If the key is a Hash, then keys will be sorted alphabetically.
-
13
def expanded_key(key)
-
return key.cache_key.to_s if key.respond_to?(:cache_key)
-
-
case key
-
when Array
-
if key.size > 1
-
key.collect { |element| expanded_key(element) }
-
else
-
expanded_key(key.first)
-
end
-
when Hash
-
key.collect { |k, v| "#{k}=#{v}" }.sort
-
else
-
key
-
end.to_param
-
end
-
-
13
def normalize_version(key, options = nil)
-
(options && options[:version].try(:to_param)) || expanded_version(key)
-
end
-
-
13
def expanded_version(key)
-
case
-
when key.respond_to?(:cache_version) then key.cache_version.to_param
-
when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
-
when key.respond_to?(:to_a) then expanded_version(key.to_a)
-
end
-
end
-
-
13
def instrument(operation, key, options = nil)
-
if logger && logger.debug? && !silence?
-
logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
-
end
-
-
payload = { key: key }
-
payload.merge!(options) if options.is_a?(Hash)
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
-
end
-
-
13
def handle_expired_entry(entry, key, options)
-
if entry && entry.expired?
-
race_ttl = options[:race_condition_ttl].to_i
-
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
-
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
-
# for a brief period while the entry is being recalculated.
-
entry.expires_at = Time.now + race_ttl
-
write_entry(key, entry, expires_in: race_ttl * 2)
-
else
-
delete_entry(key, **options)
-
end
-
entry = nil
-
end
-
entry
-
end
-
-
13
def get_entry_value(entry, name, options)
-
instrument(:fetch_hit, name, options) { }
-
entry.value
-
end
-
-
13
def save_block_result_to_cache(name, options)
-
result = instrument(:generate, name, options) do
-
yield(name)
-
end
-
-
write(name, result, options) unless result.nil? && options[:skip_nil]
-
result
-
end
-
end
-
-
# This class is used to represent cache entries. Cache entries have a value, an optional
-
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
-
# on the cache. The version is used to support the :version option on the cache for rejecting
-
# mismatches.
-
#
-
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
-
# using short instance variable names that are lazily defined.
-
13
class Entry # :nodoc:
-
13
attr_reader :version
-
-
13
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
-
-
# Creates a new cache entry for the specified value. Options supported are
-
# +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
-
13
def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
-
@value = value
-
@version = version
-
@created_at = Time.now.to_f
-
@expires_in = expires_in && expires_in.to_f
-
-
compress!(compress_threshold) if compress
-
end
-
-
13
def value
-
compressed? ? uncompress(@value) : @value
-
end
-
-
13
def mismatched?(version)
-
@version && version && @version != version
-
end
-
-
# Checks if the entry is expired. The +expires_in+ parameter can override
-
# the value set when the entry was created.
-
13
def expired?
-
@expires_in && @created_at + @expires_in <= Time.now.to_f
-
end
-
-
13
def expires_at
-
@expires_in ? @created_at + @expires_in : nil
-
end
-
-
13
def expires_at=(value)
-
if value
-
@expires_in = value.to_f - @created_at
-
else
-
@expires_in = nil
-
end
-
end
-
-
# Returns the size of the cached value. This could be less than
-
# <tt>value.size</tt> if the data is compressed.
-
13
def size
-
case value
-
when NilClass
-
0
-
when String
-
@value.bytesize
-
else
-
@s ||= Marshal.dump(@value).bytesize
-
end
-
end
-
-
# Duplicates the value in a class. This is used by cache implementations that don't natively
-
# serialize entries to protect against accidental cache modifications.
-
13
def dup_value!
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
-
if @value.is_a?(String)
-
@value = @value.dup
-
else
-
@value = Marshal.load(Marshal.dump(@value))
-
end
-
end
-
end
-
-
13
private
-
13
def compress!(compress_threshold)
-
case @value
-
when nil, true, false, Numeric
-
uncompressed_size = 0
-
when String
-
uncompressed_size = @value.bytesize
-
else
-
serialized = Marshal.dump(@value)
-
uncompressed_size = serialized.bytesize
-
end
-
-
if uncompressed_size >= compress_threshold
-
serialized ||= Marshal.dump(@value)
-
compressed = Zlib::Deflate.deflate(serialized)
-
-
if compressed.bytesize < uncompressed_size
-
@value = compressed
-
@compressed = true
-
end
-
end
-
end
-
-
13
def compressed?
-
defined?(@compressed)
-
end
-
-
13
def uncompress(value)
-
Marshal.load(Zlib::Inflate.inflate(value))
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/marshal"
-
require "active_support/core_ext/file/atomic"
-
require "active_support/core_ext/string/conversions"
-
require "uri/common"
-
-
module ActiveSupport
-
module Cache
-
# A cache store implementation which stores everything on the filesystem.
-
#
-
# FileStore implements the Strategy::LocalCache strategy which implements
-
# an in-memory cache inside of a block.
-
class FileStore < Store
-
prepend Strategy::LocalCache
-
attr_reader :cache_path
-
-
DIR_FORMATTER = "%03X"
-
FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write)
-
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
-
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
-
-
def initialize(cache_path, options = nil)
-
super(options)
-
@cache_path = cache_path.to_s
-
end
-
-
# Advertise cache versioning support.
-
def self.supports_cache_versioning?
-
true
-
end
-
-
# Deletes all items from the cache. In this case it deletes all the entries in the specified
-
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
-
# config file when using +FileStore+ because everything in that directory will be deleted.
-
def clear(options = nil)
-
root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
-
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
-
rescue Errno::ENOENT, Errno::ENOTEMPTY
-
end
-
-
# Preemptively iterates through all stored keys and removes the ones which have expired.
-
def cleanup(options = nil)
-
options = merged_options(options)
-
search_dir(cache_path) do |fname|
-
entry = read_entry(fname, **options)
-
delete_entry(fname, **options) if entry && entry.expired?
-
end
-
end
-
-
# Increments an already existing integer value that is stored in the cache.
-
# If the key is not found nothing is done.
-
def increment(name, amount = 1, options = nil)
-
modify_value(name, amount, options)
-
end
-
-
# Decrements an already existing integer value that is stored in the cache.
-
# If the key is not found nothing is done.
-
def decrement(name, amount = 1, options = nil)
-
modify_value(name, -amount, options)
-
end
-
-
def delete_matched(matcher, options = nil)
-
options = merged_options(options)
-
instrument(:delete_matched, matcher.inspect) do
-
matcher = key_matcher(matcher, options)
-
search_dir(cache_path) do |path|
-
key = file_path_key(path)
-
delete_entry(path, **options) if key.match(matcher)
-
end
-
end
-
end
-
-
private
-
def read_entry(key, **options)
-
if File.exist?(key)
-
entry = File.open(key) { |f| Marshal.load(f) }
-
entry if entry.is_a?(Cache::Entry)
-
end
-
rescue => e
-
logger.error("FileStoreError (#{e}): #{e.message}") if logger
-
nil
-
end
-
-
def write_entry(key, entry, **options)
-
return false if options[:unless_exist] && File.exist?(key)
-
ensure_cache_path(File.dirname(key))
-
File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
-
true
-
end
-
-
def delete_entry(key, **options)
-
if File.exist?(key)
-
begin
-
File.delete(key)
-
delete_empty_directories(File.dirname(key))
-
true
-
rescue => e
-
# Just in case the error was caused by another process deleting the file first.
-
raise e if File.exist?(key)
-
false
-
end
-
end
-
end
-
-
# Lock a file for a block so only one process can modify it at a time.
-
def lock_file(file_name, &block)
-
if File.exist?(file_name)
-
File.open(file_name, "r+") do |f|
-
f.flock File::LOCK_EX
-
yield
-
ensure
-
f.flock File::LOCK_UN
-
end
-
else
-
yield
-
end
-
end
-
-
# Translate a key into a file path.
-
def normalize_key(key, options)
-
key = super
-
fname = URI.encode_www_form_component(key)
-
-
if fname.size > FILEPATH_MAX_SIZE
-
fname = ActiveSupport::Digest.hexdigest(key)
-
end
-
-
hash = Zlib.adler32(fname)
-
hash, dir_1 = hash.divmod(0x1000)
-
dir_2 = hash.modulo(0x1000)
-
-
# Make sure file name doesn't exceed file system limits.
-
if fname.length < FILENAME_MAX_SIZE
-
fname_paths = fname
-
else
-
fname_paths = []
-
begin
-
fname_paths << fname[0, FILENAME_MAX_SIZE]
-
fname = fname[FILENAME_MAX_SIZE..-1]
-
end until fname.blank?
-
end
-
-
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
-
end
-
-
# Translate a file path into a key.
-
def file_path_key(path)
-
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
-
URI.decode_www_form_component(fname, Encoding::UTF_8)
-
end
-
-
# Delete empty directories in the cache.
-
def delete_empty_directories(dir)
-
return if File.realpath(dir) == File.realpath(cache_path)
-
if Dir.children(dir).empty?
-
Dir.delete(dir) rescue nil
-
delete_empty_directories(File.dirname(dir))
-
end
-
end
-
-
# Make sure a file path's directories exist.
-
def ensure_cache_path(path)
-
FileUtils.makedirs(path) unless File.exist?(path)
-
end
-
-
def search_dir(dir, &callback)
-
return if !File.exist?(dir)
-
Dir.each_child(dir) do |d|
-
name = File.join(dir, d)
-
if File.directory?(name)
-
search_dir(name, &callback)
-
else
-
callback.call name
-
end
-
end
-
end
-
-
# Modifies the amount of an already existing integer value that is stored in the cache.
-
# If the key is not found nothing is done.
-
def modify_value(name, amount, options)
-
file_name = normalize_key(name, options)
-
-
lock_file(file_name) do
-
options = merged_options(options)
-
-
if num = read(name, options)
-
num = num.to_i + amount
-
write(name, num, options)
-
num
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
begin
-
require "dalli"
-
rescue LoadError => e
-
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
-
require "active_support/core_ext/enumerable"
-
require "active_support/core_ext/marshal"
-
require "active_support/core_ext/array/extract_options"
-
-
module ActiveSupport
-
module Cache
-
# A cache store implementation which stores data in Memcached:
-
# https://memcached.org
-
#
-
# This is currently the most popular cache store for production websites.
-
#
-
# Special features:
-
# - Clustering and load balancing. One can specify multiple memcached servers,
-
# and MemCacheStore will load balance between all available servers. If a
-
# server goes down, then MemCacheStore will ignore it until it comes back up.
-
#
-
# MemCacheStore implements the Strategy::LocalCache strategy which implements
-
# an in-memory cache inside of a block.
-
class MemCacheStore < Store
-
# Provide support for raw values in the local cache strategy.
-
module LocalCacheWithRaw # :nodoc:
-
private
-
def write_entry(key, entry, **options)
-
if options[:raw] && local_cache
-
raw_entry = Entry.new(entry.value.to_s)
-
raw_entry.expires_at = entry.expires_at
-
super(key, raw_entry, **options)
-
else
-
super
-
end
-
end
-
end
-
-
# Advertise cache versioning support.
-
def self.supports_cache_versioning?
-
true
-
end
-
-
prepend Strategy::LocalCache
-
prepend LocalCacheWithRaw
-
-
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
-
-
# Creates a new Dalli::Client instance with specified addresses and options.
-
# By default address is equal localhost:11211.
-
#
-
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
-
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
-
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
-
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
-
def self.build_mem_cache(*addresses) # :nodoc:
-
addresses = addresses.flatten
-
options = addresses.extract_options!
-
addresses = ["localhost:11211"] if addresses.empty?
-
pool_options = retrieve_pool_options(options)
-
-
if pool_options.empty?
-
Dalli::Client.new(addresses, options)
-
else
-
ensure_connection_pool_added!
-
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
-
end
-
end
-
-
# Creates a new MemCacheStore object, with the given memcached server
-
# addresses. Each address is either a host name, or a host-with-port string
-
# in the form of "host_name:port". For example:
-
#
-
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
-
#
-
# If no addresses are specified, then MemCacheStore will connect to
-
# localhost port 11211 (the default memcached port).
-
def initialize(*addresses)
-
addresses = addresses.flatten
-
options = addresses.extract_options!
-
super(options)
-
-
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
-
raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
-
end
-
if addresses.first.is_a?(Dalli::Client)
-
@data = addresses.first
-
else
-
mem_cache_options = options.dup
-
UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) }
-
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
-
end
-
end
-
-
# Increment a cached value. This method uses the memcached incr atomic
-
# operator and can only be used on values written with the :raw option.
-
# Calling it on a value not stored with :raw will initialize that value
-
# to zero.
-
def increment(name, amount = 1, options = nil)
-
options = merged_options(options)
-
instrument(:increment, name, amount: amount) do
-
rescue_error_with nil do
-
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
-
end
-
end
-
end
-
-
# Decrement a cached value. This method uses the memcached decr atomic
-
# operator and can only be used on values written with the :raw option.
-
# Calling it on a value not stored with :raw will initialize that value
-
# to zero.
-
def decrement(name, amount = 1, options = nil)
-
options = merged_options(options)
-
instrument(:decrement, name, amount: amount) do
-
rescue_error_with nil do
-
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
-
end
-
end
-
end
-
-
# Clear the entire cache on all memcached servers. This method should
-
# be used with care when shared cache is being used.
-
def clear(options = nil)
-
rescue_error_with(nil) { @data.with { |c| c.flush_all } }
-
end
-
-
# Get the statistics from the memcached servers.
-
def stats
-
@data.with { |c| c.stats }
-
end
-
-
private
-
# Read an entry from the cache.
-
def read_entry(key, **options)
-
rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
-
end
-
-
# Write an entry to the cache.
-
def write_entry(key, entry, **options)
-
method = options && options[:unless_exist] ? :add : :set
-
value = options[:raw] ? entry.value.to_s : entry
-
expires_in = options[:expires_in].to_i
-
if expires_in > 0 && !options[:raw]
-
# Set the memcache expire a few minutes in the future to support race condition ttls on read
-
expires_in += 5.minutes
-
end
-
rescue_error_with false do
-
# The value "compress: false" prevents duplicate compression within Dalli.
-
@data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) }
-
end
-
end
-
-
# Reads multiple entries from the cache implementation.
-
def read_multi_entries(names, **options)
-
keys_to_names = names.index_by { |name| normalize_key(name, options) }
-
-
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
-
values = {}
-
-
raw_values.each do |key, value|
-
entry = deserialize_entry(value)
-
-
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
-
values[keys_to_names[key]] = entry.value
-
end
-
end
-
-
values
-
end
-
-
# Delete an entry from the cache.
-
def delete_entry(key, **options)
-
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
-
end
-
-
# Memcache keys are binaries. So we need to force their encoding to binary
-
# before applying the regular expression to ensure we are escaping all
-
# characters properly.
-
def normalize_key(key, options)
-
key = super.dup
-
key = key.force_encoding(Encoding::ASCII_8BIT)
-
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
-
key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
-
key
-
end
-
-
def deserialize_entry(entry)
-
if entry
-
entry.is_a?(Entry) ? entry : Entry.new(entry, compress: false)
-
end
-
end
-
-
def rescue_error_with(fallback)
-
yield
-
rescue Dalli::DalliError => e
-
logger.error("DalliError (#{e}): #{e.message}") if logger
-
fallback
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "monitor"
-
-
module ActiveSupport
-
module Cache
-
# A cache store implementation which stores everything into memory in the
-
# same process. If you're running multiple Ruby on Rails server processes
-
# (which is the case if you're using Phusion Passenger or puma clustered mode),
-
# then this means that Rails server process instances won't be able
-
# to share cache data with each other and this may not be the most
-
# appropriate cache in that scenario.
-
#
-
# This cache has a bounded size specified by the :size options to the
-
# initializer (default is 32Mb). When the cache exceeds the allotted size,
-
# a cleanup will occur which tries to prune the cache down to three quarters
-
# of the maximum size by removing the least recently used entries.
-
#
-
# MemoryStore is thread-safe.
-
class MemoryStore < Store
-
def initialize(options = nil)
-
options ||= {}
-
super(options)
-
@data = {}
-
@max_size = options[:size] || 32.megabytes
-
@max_prune_time = options[:max_prune_time] || 2
-
@cache_size = 0
-
@monitor = Monitor.new
-
@pruning = false
-
end
-
-
# Advertise cache versioning support.
-
def self.supports_cache_versioning?
-
true
-
end
-
-
# Delete all data stored in a given cache store.
-
def clear(options = nil)
-
synchronize do
-
@data.clear
-
@cache_size = 0
-
end
-
end
-
-
# Preemptively iterates through all stored keys and removes the ones which have expired.
-
def cleanup(options = nil)
-
options = merged_options(options)
-
instrument(:cleanup, size: @data.size) do
-
keys = synchronize { @data.keys }
-
keys.each do |key|
-
entry = @data[key]
-
delete_entry(key, **options) if entry && entry.expired?
-
end
-
end
-
end
-
-
# To ensure entries fit within the specified memory prune the cache by removing the least
-
# recently accessed entries.
-
def prune(target_size, max_time = nil)
-
return if pruning?
-
@pruning = true
-
begin
-
start_time = Concurrent.monotonic_time
-
cleanup
-
instrument(:prune, target_size, from: @cache_size) do
-
keys = synchronize { @data.keys }
-
keys.each do |key|
-
delete_entry(key, **options)
-
return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
-
end
-
end
-
ensure
-
@pruning = false
-
end
-
end
-
-
# Returns true if the cache is currently being pruned.
-
def pruning?
-
@pruning
-
end
-
-
# Increment an integer value in the cache.
-
def increment(name, amount = 1, options = nil)
-
modify_value(name, amount, options)
-
end
-
-
# Decrement an integer value in the cache.
-
def decrement(name, amount = 1, options = nil)
-
modify_value(name, -amount, options)
-
end
-
-
# Deletes cache entries if the cache key matches a given pattern.
-
def delete_matched(matcher, options = nil)
-
options = merged_options(options)
-
instrument(:delete_matched, matcher.inspect) do
-
matcher = key_matcher(matcher, options)
-
keys = synchronize { @data.keys }
-
keys.each do |key|
-
delete_entry(key, **options) if key.match(matcher)
-
end
-
end
-
end
-
-
def inspect # :nodoc:
-
"#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
-
end
-
-
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
-
# is not thread safe.
-
def synchronize(&block) # :nodoc:
-
@monitor.synchronize(&block)
-
end
-
-
private
-
PER_ENTRY_OVERHEAD = 240
-
-
def cached_size(key, entry)
-
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
-
end
-
-
def read_entry(key, **options)
-
entry = nil
-
synchronize do
-
entry = @data.delete(key)
-
if entry
-
@data[key] = entry
-
entry = entry.dup
-
end
-
end
-
entry&.dup_value!
-
entry
-
end
-
-
def write_entry(key, entry, **options)
-
entry.dup_value!
-
synchronize do
-
return false if options[:unless_exist] && @data.key?(key)
-
-
old_entry = @data.delete(key)
-
if old_entry
-
@cache_size -= (old_entry.size - entry.size)
-
else
-
@cache_size += cached_size(key, entry)
-
end
-
@data[key] = entry
-
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
-
true
-
end
-
end
-
-
def delete_entry(key, **options)
-
synchronize do
-
entry = @data.delete(key)
-
@cache_size -= cached_size(key, entry) if entry
-
!!entry
-
end
-
end
-
-
def modify_value(name, amount, options)
-
options = merged_options(options)
-
synchronize do
-
if num = read(name, options)
-
num = num.to_i + amount
-
write(name, num, options)
-
num
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
module Cache
-
# A cache store implementation which doesn't actually store anything. Useful in
-
# development and test environments where you don't want caching turned on but
-
# need to go through the caching interface.
-
#
-
# This cache does implement the local cache strategy, so values will actually
-
# be cached inside blocks that utilize this strategy. See
-
# ActiveSupport::Cache::Strategy::LocalCache for more details.
-
class NullStore < Store
-
prepend Strategy::LocalCache
-
-
# Advertise cache versioning support.
-
def self.supports_cache_versioning?
-
true
-
end
-
-
def clear(options = nil)
-
end
-
-
def cleanup(options = nil)
-
end
-
-
def increment(name, amount = 1, options = nil)
-
end
-
-
def decrement(name, amount = 1, options = nil)
-
end
-
-
def delete_matched(matcher, options = nil)
-
end
-
-
private
-
def read_entry(key, **options)
-
end
-
-
def write_entry(key, entry, **options)
-
true
-
end
-
-
def delete_entry(key, **options)
-
false
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
begin
-
3
gem "redis", ">= 4.0.1"
-
3
require "redis"
-
3
require "redis/distributed"
-
rescue LoadError
-
warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`"
-
raise
-
end
-
-
# Prefer the hiredis driver but don't require it.
-
3
begin
-
3
require "redis/connection/hiredis"
-
rescue LoadError
-
end
-
-
3
require "digest/sha2"
-
3
require "active_support/core_ext/marshal"
-
-
3
module ActiveSupport
-
3
module Cache
-
3
module ConnectionPoolLike
-
3
def with
-
yield self
-
end
-
end
-
-
3
::Redis.include(ConnectionPoolLike)
-
3
::Redis::Distributed.include(ConnectionPoolLike)
-
-
# Redis cache store.
-
#
-
# Deployment note: Take care to use a *dedicated Redis cache* rather
-
# than pointing this at your existing Redis server. It won't cope well
-
# with mixed usage patterns and it won't expire cache entries by default.
-
#
-
# Redis cache server setup guide: https://redis.io/topics/lru-cache
-
#
-
# * Supports vanilla Redis, hiredis, and Redis::Distributed.
-
# * Supports Memcached-like sharding across Redises with Redis::Distributed.
-
# * Fault tolerant. If the Redis server is unavailable, no exceptions are
-
# raised. Cache fetches are all misses and writes are dropped.
-
# * Local cache. Hot in-memory primary cache within block/middleware scope.
-
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
-
# 4.0.1+ for distributed mget support.
-
# * +delete_matched+ support for Redis KEYS globs.
-
3
class RedisCacheStore < Store
-
# Keys are truncated with their own SHA2 digest if they exceed 1kB
-
3
MAX_KEY_BYTESIZE = 1024
-
-
3
DEFAULT_REDIS_OPTIONS = {
-
connect_timeout: 20,
-
read_timeout: 1,
-
write_timeout: 1,
-
reconnect_attempts: 0,
-
}
-
-
3
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
-
if logger
-
logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
-
end
-
end
-
-
# The maximum number of entries to receive per SCAN call.
-
3
SCAN_BATCH_SIZE = 1000
-
3
private_constant :SCAN_BATCH_SIZE
-
-
# Advertise cache versioning support.
-
3
def self.supports_cache_versioning?
-
true
-
end
-
-
# Support raw values in the local cache strategy.
-
3
module LocalCacheWithRaw # :nodoc:
-
3
private
-
3
def write_entry(key, entry, **options)
-
if options[:raw] && local_cache
-
raw_entry = Entry.new(serialize_entry(entry, raw: true))
-
raw_entry.expires_at = entry.expires_at
-
super(key, raw_entry, **options)
-
else
-
super
-
end
-
end
-
-
3
def write_multi_entries(entries, **options)
-
if options[:raw] && local_cache
-
raw_entries = entries.map do |key, entry|
-
raw_entry = Entry.new(serialize_entry(entry, raw: true))
-
raw_entry.expires_at = entry.expires_at
-
end.to_h
-
-
super(raw_entries, **options)
-
else
-
super
-
end
-
end
-
end
-
-
3
prepend Strategy::LocalCache
-
3
prepend LocalCacheWithRaw
-
-
3
class << self
-
# Factory method to create a new Redis instance.
-
#
-
# Handles four options: :redis block, :redis instance, single :url
-
# string, and multiple :url strings.
-
#
-
# Option Class Result
-
# :redis Proc -> options[:redis].call
-
# :redis Object -> options[:redis]
-
# :url String -> Redis.new(url: …)
-
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
-
#
-
3
def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
-
urls = Array(url)
-
-
if redis.is_a?(Proc)
-
redis.call
-
elsif redis
-
redis
-
elsif urls.size > 1
-
build_redis_distributed_client urls: urls, **redis_options
-
else
-
build_redis_client url: urls.first, **redis_options
-
end
-
end
-
-
3
private
-
3
def build_redis_distributed_client(urls:, **redis_options)
-
::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
-
urls.each { |u| dist.add_node url: u }
-
end
-
end
-
-
3
def build_redis_client(url:, **redis_options)
-
::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
-
end
-
end
-
-
3
attr_reader :redis_options
-
3
attr_reader :max_key_bytesize
-
-
# Creates a new Redis cache store.
-
#
-
# Handles four options: :redis block, :redis instance, single :url
-
# string, and multiple :url strings.
-
#
-
# Option Class Result
-
# :redis Proc -> options[:redis].call
-
# :redis Object -> options[:redis]
-
# :url String -> Redis.new(url: …)
-
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
-
#
-
# No namespace is set by default. Provide one if the Redis cache
-
# server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
-
#
-
# Compression is enabled by default with a 1kB threshold, so cached
-
# values larger than 1kB are automatically compressed. Disable by
-
# passing <tt>compress: false</tt> or change the threshold by passing
-
# <tt>compress_threshold: 4.kilobytes</tt>.
-
#
-
# No expiry is set on cache entries by default. Redis is expected to
-
# be configured with an eviction policy that automatically deletes
-
# least-recently or -frequently used keys when it reaches max memory.
-
# See https://redis.io/topics/lru-cache for cache server setup.
-
#
-
# Race condition TTL is not set by default. This can be used to avoid
-
# "thundering herd" cache writes when hot cache entries are expired.
-
# See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
-
3
def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
-
@redis_options = redis_options
-
-
@max_key_bytesize = MAX_KEY_BYTESIZE
-
@error_handler = error_handler
-
-
super namespace: namespace,
-
compress: compress, compress_threshold: compress_threshold,
-
expires_in: expires_in, race_condition_ttl: race_condition_ttl
-
end
-
-
3
def redis
-
@redis ||= begin
-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
-
-
if pool_options.any?
-
self.class.send(:ensure_connection_pool_added!)
-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
-
else
-
self.class.build_redis(**redis_options)
-
end
-
end
-
end
-
-
3
def inspect
-
instance = @redis || @redis_options
-
"#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
-
end
-
-
# Cache Store API implementation.
-
#
-
# Read multiple values at once. Returns a hash of requested keys ->
-
# fetched values.
-
3
def read_multi(*names)
-
if mget_capable?
-
instrument(:read_multi, names, options) do |payload|
-
read_multi_mget(*names).tap do |results|
-
payload[:hits] = results.keys
-
end
-
end
-
else
-
super
-
end
-
end
-
-
# Cache Store API implementation.
-
#
-
# Supports Redis KEYS glob patterns:
-
#
-
# h?llo matches hello, hallo and hxllo
-
# h*llo matches hllo and heeeello
-
# h[ae]llo matches hello and hallo, but not hillo
-
# h[^e]llo matches hallo, hbllo, ... but not hello
-
# h[a-b]llo matches hallo and hbllo
-
#
-
# Use \ to escape special characters if you want to match them verbatim.
-
#
-
# See https://redis.io/commands/KEYS for more.
-
#
-
# Failsafe: Raises errors.
-
3
def delete_matched(matcher, options = nil)
-
instrument :delete_matched, matcher do
-
unless String === matcher
-
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
-
end
-
redis.with do |c|
-
pattern = namespace_key(matcher, options)
-
cursor = "0"
-
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
-
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
-
-
nodes.each do |node|
-
begin
-
cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
-
node.del(*keys) unless keys.empty?
-
end until cursor == "0"
-
end
-
end
-
end
-
end
-
-
# Cache Store API implementation.
-
#
-
# Increment a cached value. This method uses the Redis incr atomic
-
# operator and can only be used on values written with the :raw option.
-
# Calling it on a value not stored with :raw will initialize that value
-
# to zero.
-
#
-
# Failsafe: Raises errors.
-
3
def increment(name, amount = 1, options = nil)
-
instrument :increment, name, amount: amount do
-
failsafe :increment do
-
options = merged_options(options)
-
key = normalize_key(name, options)
-
-
redis.with do |c|
-
c.incrby(key, amount).tap do
-
write_key_expiry(c, key, options)
-
end
-
end
-
end
-
end
-
end
-
-
# Cache Store API implementation.
-
#
-
# Decrement a cached value. This method uses the Redis decr atomic
-
# operator and can only be used on values written with the :raw option.
-
# Calling it on a value not stored with :raw will initialize that value
-
# to zero.
-
#
-
# Failsafe: Raises errors.
-
3
def decrement(name, amount = 1, options = nil)
-
instrument :decrement, name, amount: amount do
-
failsafe :decrement do
-
options = merged_options(options)
-
key = normalize_key(name, options)
-
-
redis.with do |c|
-
c.decrby(key, amount).tap do
-
write_key_expiry(c, key, options)
-
end
-
end
-
end
-
end
-
end
-
-
# Cache Store API implementation.
-
#
-
# Removes expired entries. Handled natively by Redis least-recently-/
-
# least-frequently-used expiry, so manual cleanup is not supported.
-
3
def cleanup(options = nil)
-
super
-
end
-
-
# Clear the entire cache on all Redis servers. Safe to use on
-
# shared servers if the cache is namespaced.
-
#
-
# Failsafe: Raises errors.
-
3
def clear(options = nil)
-
failsafe :clear do
-
if namespace = merged_options(options)[:namespace]
-
delete_matched "*", namespace: namespace
-
else
-
redis.with { |c| c.flushdb }
-
end
-
end
-
end
-
-
3
def mget_capable? #:nodoc:
-
set_redis_capabilities unless defined? @mget_capable
-
@mget_capable
-
end
-
-
3
def mset_capable? #:nodoc:
-
set_redis_capabilities unless defined? @mset_capable
-
@mset_capable
-
end
-
-
3
private
-
3
def set_redis_capabilities
-
case redis
-
when Redis::Distributed
-
@mget_capable = true
-
@mset_capable = false
-
else
-
@mget_capable = true
-
@mset_capable = true
-
end
-
end
-
-
# Store provider interface:
-
# Read an entry from the cache.
-
3
def read_entry(key, **options)
-
failsafe :read_entry do
-
raw = options&.fetch(:raw, false)
-
deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
-
end
-
end
-
-
3
def read_multi_entries(names, **options)
-
if mget_capable?
-
read_multi_mget(*names, **options)
-
else
-
super
-
end
-
end
-
-
3
def read_multi_mget(*names)
-
options = names.extract_options!
-
options = merged_options(options)
-
return {} if names == []
-
raw = options&.fetch(:raw, false)
-
-
keys = names.map { |name| normalize_key(name, options) }
-
-
values = failsafe(:read_multi_mget, returning: {}) do
-
redis.with { |c| c.mget(*keys) }
-
end
-
-
names.zip(values).each_with_object({}) do |(name, value), results|
-
if value
-
entry = deserialize_entry(value, raw: raw)
-
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
-
results[name] = entry.value
-
end
-
end
-
end
-
end
-
-
# Write an entry to the cache.
-
#
-
# Requires Redis 2.6.12+ for extended SET options.
-
3
def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
-
serialized_entry = serialize_entry(entry, raw: raw)
-
-
# If race condition TTL is in use, ensure that cache entries
-
# stick around a bit longer after they would have expired
-
# so we can purposefully serve stale entries.
-
if race_condition_ttl && expires_in && expires_in > 0 && !raw
-
expires_in += 5.minutes
-
end
-
-
failsafe :write_entry, returning: false do
-
if unless_exist || expires_in
-
modifiers = {}
-
modifiers[:nx] = unless_exist
-
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
-
-
redis.with { |c| c.set key, serialized_entry, **modifiers }
-
else
-
redis.with { |c| c.set key, serialized_entry }
-
end
-
end
-
end
-
-
3
def write_key_expiry(client, key, options)
-
if options[:expires_in] && client.ttl(key).negative?
-
client.expire key, options[:expires_in].to_i
-
end
-
end
-
-
# Delete an entry from the cache.
-
3
def delete_entry(key, options)
-
failsafe :delete_entry, returning: false do
-
redis.with { |c| c.del key }
-
end
-
end
-
-
# Deletes multiple entries in the cache. Returns the number of entries deleted.
-
3
def delete_multi_entries(entries, **_options)
-
redis.with { |c| c.del(entries) }
-
end
-
-
# Nonstandard store provider API to write multiple values at once.
-
3
def write_multi_entries(entries, expires_in: nil, **options)
-
if entries.any?
-
if mset_capable? && expires_in.nil?
-
failsafe :write_multi_entries do
-
redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) }
-
end
-
else
-
super
-
end
-
end
-
end
-
-
# Truncate keys that exceed 1kB.
-
3
def normalize_key(key, options)
-
truncate_key super&.b
-
end
-
-
3
def truncate_key(key)
-
if key && key.bytesize > max_key_bytesize
-
suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
-
truncate_at = max_key_bytesize - suffix.bytesize
-
"#{key.byteslice(0, truncate_at)}#{suffix}"
-
else
-
key
-
end
-
end
-
-
3
def deserialize_entry(serialized_entry, raw:)
-
if serialized_entry
-
if raw
-
Entry.new(serialized_entry, compress: false)
-
else
-
Marshal.load(serialized_entry)
-
end
-
end
-
end
-
-
3
def serialize_entry(entry, raw: false)
-
if raw
-
entry.value.to_s
-
else
-
Marshal.dump(entry)
-
end
-
end
-
-
3
def serialize_entries(entries, raw: false)
-
entries.transform_values do |entry|
-
serialize_entry entry, raw: raw
-
end
-
end
-
-
3
def failsafe(method, returning: nil)
-
yield
-
rescue ::Redis::BaseError => e
-
handle_exception exception: e, method: method, returning: returning
-
returning
-
end
-
-
3
def handle_exception(exception:, method:, returning:)
-
if @error_handler
-
@error_handler.(method: method, exception: exception, returning: returning)
-
end
-
rescue => failsafe
-
warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
4
require "active_support/core_ext/string/inflections"
-
4
require "active_support/per_thread_registry"
-
-
4
module ActiveSupport
-
4
module Cache
-
4
module Strategy
-
# Caches that implement LocalCache will be backed by an in-memory cache for the
-
# duration of a block. Repeated calls to the cache for the same key will hit the
-
# in-memory cache for faster access.
-
4
module LocalCache
-
4
autoload :Middleware, "active_support/cache/strategy/local_cache_middleware"
-
-
# Class for storing and registering the local caches.
-
4
class LocalCacheRegistry # :nodoc:
-
4
extend ActiveSupport::PerThreadRegistry
-
-
4
def initialize
-
@registry = {}
-
end
-
-
4
def cache_for(local_cache_key)
-
@registry[local_cache_key]
-
end
-
-
4
def set_cache_for(local_cache_key, value)
-
@registry[local_cache_key] = value
-
end
-
-
4
def self.set_cache_for(l, v); instance.set_cache_for l, v; end
-
4
def self.cache_for(l); instance.cache_for l; end
-
end
-
-
# Simple memory backed cache. This cache is not thread safe and is intended only
-
# for serving as a temporary memory cache for a single thread.
-
4
class LocalStore < Store
-
4
def initialize
-
super
-
@data = {}
-
end
-
-
# Don't allow synchronizing since it isn't thread safe.
-
4
def synchronize # :nodoc:
-
yield
-
end
-
-
4
def clear(options = nil)
-
@data.clear
-
end
-
-
4
def read_entry(key, **options)
-
@data[key]
-
end
-
-
4
def read_multi_entries(keys, **options)
-
values = {}
-
-
keys.each do |name|
-
entry = read_entry(name, **options)
-
values[name] = entry.value if entry
-
end
-
-
values
-
end
-
-
4
def write_entry(key, entry, **options)
-
entry.dup_value!
-
@data[key] = entry
-
true
-
end
-
-
4
def delete_entry(key, **options)
-
!!@data.delete(key)
-
end
-
-
4
def fetch_entry(key, options = nil) # :nodoc:
-
entry = @data.fetch(key) { @data[key] = yield }
-
dup_entry = entry.dup
-
dup_entry&.dup_value!
-
dup_entry
-
end
-
end
-
-
# Use a local cache for the duration of block.
-
4
def with_local_cache
-
use_temporary_local_cache(LocalStore.new) { yield }
-
end
-
-
# Middleware class can be inserted as a Rack handler to be local cache for the
-
# duration of request.
-
4
def middleware
-
@middleware ||= Middleware.new(
-
"ActiveSupport::Cache::Strategy::LocalCache",
-
local_cache_key)
-
end
-
-
4
def clear(**options) # :nodoc:
-
return super unless cache = local_cache
-
cache.clear(options)
-
super
-
end
-
-
4
def cleanup(**options) # :nodoc:
-
return super unless cache = local_cache
-
cache.clear
-
super
-
end
-
-
4
def increment(name, amount = 1, **options) # :nodoc:
-
return super unless local_cache
-
value = bypass_local_cache { super }
-
write_cache_value(name, value, **options)
-
value
-
end
-
-
4
def decrement(name, amount = 1, **options) # :nodoc:
-
return super unless local_cache
-
value = bypass_local_cache { super }
-
write_cache_value(name, value, **options)
-
value
-
end
-
-
4
private
-
4
def read_entry(key, **options)
-
if cache = local_cache
-
cache.fetch_entry(key) { super }
-
else
-
super
-
end
-
end
-
-
4
def read_multi_entries(keys, **options)
-
return super unless local_cache
-
-
local_entries = local_cache.read_multi_entries(keys, **options)
-
missed_keys = keys - local_entries.keys
-
-
if missed_keys.any?
-
local_entries.merge!(super(missed_keys, **options))
-
else
-
local_entries
-
end
-
end
-
-
4
def write_entry(key, entry, **options)
-
if options[:unless_exist]
-
local_cache.delete_entry(key, **options) if local_cache
-
else
-
local_cache.write_entry(key, entry, **options) if local_cache
-
end
-
-
super
-
end
-
-
4
def delete_entry(key, **options)
-
local_cache.delete_entry(key, **options) if local_cache
-
super
-
end
-
-
4
def write_cache_value(name, value, **options)
-
name = normalize_key(name, options)
-
cache = local_cache
-
cache.mute do
-
if value
-
cache.write(name, value, options)
-
else
-
cache.delete(name, **options)
-
end
-
end
-
end
-
-
4
def local_cache_key
-
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
-
end
-
-
4
def local_cache
-
LocalCacheRegistry.cache_for(local_cache_key)
-
end
-
-
4
def bypass_local_cache
-
use_temporary_local_cache(nil) { yield }
-
end
-
-
4
def use_temporary_local_cache(temporary_cache)
-
save_cache = LocalCacheRegistry.cache_for(local_cache_key)
-
begin
-
LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache)
-
yield
-
ensure
-
LocalCacheRegistry.set_cache_for(local_cache_key, save_cache)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rack/body_proxy"
-
require "rack/utils"
-
-
module ActiveSupport
-
module Cache
-
module Strategy
-
module LocalCache
-
#--
-
# This class wraps up local storage for middlewares. Only the middleware method should
-
# construct them.
-
class Middleware # :nodoc:
-
attr_reader :name, :local_cache_key
-
-
def initialize(name, local_cache_key)
-
@name = name
-
@local_cache_key = local_cache_key
-
@app = nil
-
end
-
-
def new(app)
-
@app = app
-
self
-
end
-
-
def call(env)
-
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
-
response = @app.call(env)
-
response[2] = ::Rack::BodyProxy.new(response[2]) do
-
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
-
end
-
cleanup_on_body_close = true
-
response
-
rescue Rack::Utils::InvalidParameterError
-
[400, {}, []]
-
ensure
-
LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
-
cleanup_on_body_close
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/concern"
-
23
require "active_support/descendants_tracker"
-
23
require "active_support/core_ext/array/extract_options"
-
23
require "active_support/core_ext/class/attribute"
-
23
require "active_support/core_ext/string/filters"
-
23
require "thread"
-
-
23
module ActiveSupport
-
# Callbacks are code hooks that are run at key points in an object's life cycle.
-
# The typical use case is to have a base class define a set of callbacks
-
# relevant to the other functionality it supplies, so that subclasses can
-
# install callbacks that enhance or modify the base functionality without
-
# needing to override or redefine methods of the base class.
-
#
-
# Mixing in this module allows you to define the events in the object's
-
# life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
-
# set the instance methods, procs, or callback objects to be called (via
-
# +ClassMethods.set_callback+), and run the installed callbacks at the
-
# appropriate times (via +run_callbacks+).
-
#
-
# By default callbacks are halted by throwing +:abort+.
-
# See +ClassMethods.define_callbacks+ for details.
-
#
-
# Three kinds of callbacks are supported: before callbacks, run before a
-
# certain event; after callbacks, run after the event; and around callbacks,
-
# blocks that surround the event, triggering it when they yield. Callback code
-
# can be contained in instance methods, procs or lambdas, or callback objects
-
# that respond to certain predetermined methods. See +ClassMethods.set_callback+
-
# for details.
-
#
-
# class Record
-
# include ActiveSupport::Callbacks
-
# define_callbacks :save
-
#
-
# def save
-
# run_callbacks :save do
-
# puts "- save"
-
# end
-
# end
-
# end
-
#
-
# class PersonRecord < Record
-
# set_callback :save, :before, :saving_message
-
# def saving_message
-
# puts "saving..."
-
# end
-
#
-
# set_callback :save, :after do |object|
-
# puts "saved"
-
# end
-
# end
-
#
-
# person = PersonRecord.new
-
# person.save
-
#
-
# Output:
-
# saving...
-
# - save
-
# saved
-
23
module Callbacks
-
23
extend Concern
-
-
23
included do
-
40
extend ActiveSupport::DescendantsTracker
-
40
class_attribute :__callbacks, instance_writer: false, default: {}
-
end
-
-
23
CALLBACK_FILTER_TYPES = [:before, :after, :around]
-
-
# Runs the callbacks for the given event.
-
#
-
# Calls the before and around callbacks in the order they were set, yields
-
# the block (if given one), and then runs the after callbacks in reverse
-
# order.
-
#
-
# If the callback chain was halted, returns +false+. Otherwise returns the
-
# result of the block, +nil+ if no callbacks have been set, or +true+
-
# if callbacks have been set but no block is given.
-
#
-
# run_callbacks :save do
-
# save
-
# end
-
#
-
#--
-
#
-
# As this method is used in many places, and often wraps large portions of
-
# user code, it has an additional design goal of minimizing its impact on
-
# the visible call stack. An exception from inside a :before or :after
-
# callback can be as noisy as it likes -- but when control has passed
-
# smoothly through and into the supplied block, we want as little evidence
-
# as possible that we were here.
-
23
def run_callbacks(kind)
-
callbacks = __callbacks[kind.to_sym]
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
next_sequence = callbacks.compile
-
-
# Common case: no 'around' callbacks defined
-
if next_sequence.final?
-
next_sequence.invoke_before(env)
-
env.value = !env.halted && (!block_given? || yield)
-
next_sequence.invoke_after(env)
-
env.value
-
else
-
invoke_sequence = Proc.new do
-
skipped = nil
-
-
while true
-
current = next_sequence
-
current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
next
-
else
-
next_sequence = next_sequence.nested
-
begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send(method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
end
-
current.invoke_after(env)
-
skipped.pop.invoke_after(env) while skipped&.first
-
break env.value
-
end
-
end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
23
private
-
# A hook invoked every time a before callback is halted.
-
# This can be overridden in ActiveSupport::Callbacks implementors in order
-
# to provide better debugging/logging.
-
23
def halted_callback_hook(filter, name)
-
end
-
-
23
module Conditionals # :nodoc:
-
23
class Value
-
23
def initialize(&block)
-
@block = block
-
end
-
23
def call(target, value); @block.call(value); end
-
end
-
end
-
-
23
module Filters
-
23
Environment = Struct.new(:target, :halted, :value)
-
-
23
class Before
-
23
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name)
-
halted_lambda = chain_config[:terminator]
-
-
if user_conditions.any?
-
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
-
else
-
halting(callback_sequence, user_callback, halted_lambda, filter, name)
-
end
-
end
-
-
23
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
-
callback_sequence.before do |env|
-
target = env.target
-
value = env.value
-
halted = env.halted
-
-
if !halted && user_conditions.all? { |c| c.call(target, value) }
-
result_lambda = -> { user_callback.call target, value }
-
env.halted = halted_lambda.call(target, result_lambda)
-
if env.halted
-
target.send :halted_callback_hook, filter, name
-
end
-
end
-
-
env
-
end
-
end
-
23
private_class_method :halting_and_conditional
-
-
23
def self.halting(callback_sequence, user_callback, halted_lambda, filter, name)
-
callback_sequence.before do |env|
-
target = env.target
-
value = env.value
-
halted = env.halted
-
-
unless halted
-
result_lambda = -> { user_callback.call target, value }
-
env.halted = halted_lambda.call(target, result_lambda)
-
if env.halted
-
target.send :halted_callback_hook, filter, name
-
end
-
end
-
-
env
-
end
-
end
-
23
private_class_method :halting
-
end
-
-
23
class After
-
23
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
-
if chain_config[:skip_after_callbacks_if_terminated]
-
if user_conditions.any?
-
halting_and_conditional(callback_sequence, user_callback, user_conditions)
-
else
-
halting(callback_sequence, user_callback)
-
end
-
else
-
if user_conditions.any?
-
conditional callback_sequence, user_callback, user_conditions
-
else
-
simple callback_sequence, user_callback
-
end
-
end
-
end
-
-
23
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
-
callback_sequence.after do |env|
-
target = env.target
-
value = env.value
-
halted = env.halted
-
-
if !halted && user_conditions.all? { |c| c.call(target, value) }
-
user_callback.call target, value
-
end
-
-
env
-
end
-
end
-
23
private_class_method :halting_and_conditional
-
-
23
def self.halting(callback_sequence, user_callback)
-
callback_sequence.after do |env|
-
unless env.halted
-
user_callback.call env.target, env.value
-
end
-
-
env
-
end
-
end
-
23
private_class_method :halting
-
-
23
def self.conditional(callback_sequence, user_callback, user_conditions)
-
callback_sequence.after do |env|
-
target = env.target
-
value = env.value
-
-
if user_conditions.all? { |c| c.call(target, value) }
-
user_callback.call target, value
-
end
-
-
env
-
end
-
end
-
23
private_class_method :conditional
-
-
23
def self.simple(callback_sequence, user_callback)
-
callback_sequence.after do |env|
-
user_callback.call env.target, env.value
-
-
env
-
end
-
end
-
23
private_class_method :simple
-
end
-
end
-
-
23
class Callback #:nodoc:#
-
23
def self.build(chain, filter, kind, options)
-
154
if filter.is_a?(String)
-
raise ArgumentError, <<-MSG.squish
-
Passing string to define a callback is not supported. See the `.set_callback`
-
documentation to see supported values.
-
MSG
-
end
-
-
154
new chain.name, filter, kind, options, chain.config
-
end
-
-
23
attr_accessor :kind, :name
-
23
attr_reader :chain_config
-
-
23
def initialize(name, filter, kind, options, chain_config)
-
154
@chain_config = chain_config
-
154
@name = name
-
154
@kind = kind
-
154
@filter = filter
-
154
@key = compute_identifier filter
-
154
@if = check_conditionals(options[:if])
-
154
@unless = check_conditionals(options[:unless])
-
end
-
-
265
def filter; @key; end
-
23
def raw_filter; @filter; end
-
-
23
def merge_conditional_options(chain, if_option:, unless_option:)
-
13
options = {
-
if: @if.dup,
-
unless: @unless.dup
-
}
-
-
13
options[:if].concat Array(unless_option)
-
13
options[:unless].concat Array(if_option)
-
-
13
self.class.build chain, @filter, @kind, options
-
end
-
-
23
def matches?(_kind, _filter)
-
196
@kind == _kind && filter == _filter
-
end
-
-
23
def duplicates?(other)
-
253
case @filter
-
when Symbol
-
158
matches?(other.kind, other.filter)
-
else
-
95
false
-
end
-
end
-
-
# Wraps code with filter
-
23
def apply(callback_sequence)
-
user_conditions = conditions_lambdas
-
user_callback = CallTemplate.build(@filter, self)
-
-
case kind
-
when :before
-
Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name)
-
when :after
-
Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
-
when :around
-
callback_sequence.around(user_callback, user_conditions)
-
end
-
end
-
-
23
def current_scopes
-
Array(chain_config[:scope]).map { |s| public_send(s) }
-
end
-
-
23
private
-
23
EMPTY_ARRAY = [].freeze
-
23
private_constant :EMPTY_ARRAY
-
-
23
def check_conditionals(conditionals)
-
308
return EMPTY_ARRAY if conditionals.blank?
-
-
51
conditionals = Array(conditionals)
-
109
if conditionals.any? { |c| c.is_a?(String) }
-
raise ArgumentError, <<-MSG.squish
-
Passing string to be evaluated in :if and :unless conditional
-
options is not supported. Pass a symbol for an instance method,
-
or a lambda, proc or block, instead.
-
MSG
-
end
-
-
51
conditionals.freeze
-
end
-
-
23
def compute_identifier(filter)
-
154
case filter
-
when ::Proc
-
58
filter.object_id
-
else
-
96
filter
-
end
-
end
-
-
23
def conditions_lambdas
-
@if.map { |c| CallTemplate.build(c, self).make_lambda } +
-
@unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
-
end
-
end
-
-
# A future invocation of user-supplied code (either as a callback,
-
# or a condition filter).
-
23
class CallTemplate # :nodoc:
-
23
def initialize(target, method, arguments, block)
-
@override_target = target
-
@method_name = method
-
@arguments = arguments
-
@override_block = block
-
end
-
-
# Return the parts needed to make this call, with the given
-
# input values.
-
#
-
# Returns an array of the form:
-
#
-
# [target, block, method, *arguments]
-
#
-
# This array can be used as such:
-
#
-
# target.send(method, *arguments, &block)
-
#
-
# The actual invocation is left up to the caller to minimize
-
# call stack pollution.
-
23
def expand(target, value, block)
-
expanded = [@override_target || target, @override_block || block, @method_name]
-
-
@arguments.each do |arg|
-
case arg
-
when :value then expanded << value
-
when :target then expanded << target
-
when :block then expanded << (block || raise(ArgumentError))
-
end
-
end
-
-
expanded
-
end
-
-
# Return a lambda that will make this call when given the input
-
# values.
-
23
def make_lambda
-
lambda do |target, value, &block|
-
target, block, method, *arguments = expand(target, value, block)
-
target.send(method, *arguments, &block)
-
end
-
end
-
-
# Return a lambda that will make this call when given the input
-
# values, but then return the boolean inverse of that result.
-
23
def inverted_lambda
-
lambda do |target, value, &block|
-
target, block, method, *arguments = expand(target, value, block)
-
! target.send(method, *arguments, &block)
-
end
-
end
-
-
# Filters support:
-
#
-
# Symbols:: A method to call.
-
# Procs:: A proc to call with the object.
-
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
-
#
-
# All of these objects are converted into a CallTemplate and handled
-
# the same after this point.
-
23
def self.build(filter, callback)
-
case filter
-
when Symbol
-
new(nil, filter, [], nil)
-
when Conditionals::Value
-
new(filter, :call, [:target, :value], nil)
-
when ::Proc
-
if filter.arity > 1
-
new(nil, :instance_exec, [:target, :block], filter)
-
elsif filter.arity > 0
-
new(nil, :instance_exec, [:target], filter)
-
else
-
new(nil, :instance_exec, [], filter)
-
end
-
else
-
method_to_call = callback.current_scopes.join("_")
-
-
new(filter, method_to_call, [:target], nil)
-
end
-
end
-
end
-
-
# Execute before and after filters in a sequence instead of
-
# chaining them with nested lambda calls, see:
-
# https://github.com/rails/rails/issues/18011
-
23
class CallbackSequence # :nodoc:
-
23
def initialize(nested = nil, call_template = nil, user_conditions = nil)
-
@nested = nested
-
@call_template = call_template
-
@user_conditions = user_conditions
-
-
@before = []
-
@after = []
-
end
-
-
23
def before(&before)
-
@before.unshift(before)
-
self
-
end
-
-
23
def after(&after)
-
@after.push(after)
-
self
-
end
-
-
23
def around(call_template, user_conditions)
-
CallbackSequence.new(self, call_template, user_conditions)
-
end
-
-
23
def skip?(arg)
-
arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
-
end
-
-
23
attr_reader :nested
-
-
23
def final?
-
!@call_template
-
end
-
-
23
def expand_call_template(arg, block)
-
@call_template.expand(arg.target, arg.value, block)
-
end
-
-
23
def invoke_before(arg)
-
@before.each { |b| b.call(arg) }
-
end
-
-
23
def invoke_after(arg)
-
@after.each { |a| a.call(arg) }
-
end
-
end
-
-
23
class CallbackChain #:nodoc:#
-
23
include Enumerable
-
-
23
attr_reader :name, :config
-
-
23
def initialize(name, config)
-
66
@name = name
-
66
@config = {
-
scope: [:kind],
-
terminator: default_terminator
-
}.merge!(config)
-
66
@chain = []
-
66
@callbacks = nil
-
66
@mutex = Mutex.new
-
end
-
-
37
def each(&block); @chain.each(&block); end
-
36
def index(o); @chain.index(o); end
-
23
def empty?; @chain.empty?; end
-
-
23
def insert(index, o)
-
13
@callbacks = nil
-
13
@chain.insert(index, o)
-
end
-
-
23
def delete(o)
-
14
@callbacks = nil
-
14
@chain.delete(o)
-
end
-
-
23
def clear
-
1
@callbacks = nil
-
1
@chain.clear
-
1
self
-
end
-
-
23
def initialize_copy(other)
-
145
@callbacks = nil
-
145
@chain = other.chain.dup
-
145
@mutex = Mutex.new
-
end
-
-
23
def compile
-
@callbacks || @mutex.synchronize do
-
final_sequence = CallbackSequence.new
-
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
-
callback.apply callback_sequence
-
end
-
end
-
end
-
-
23
def append(*callbacks)
-
271
callbacks.each { |c| append_one(c) }
-
end
-
-
23
def prepend(*callbacks)
-
callbacks.each { |c| prepend_one(c) }
-
end
-
-
23
protected
-
23
attr_reader :chain
-
-
23
private
-
23
def append_one(callback)
-
141
@callbacks = nil
-
141
remove_duplicates(callback)
-
141
@chain.push(callback)
-
end
-
-
23
def prepend_one(callback)
-
@callbacks = nil
-
remove_duplicates(callback)
-
@chain.unshift(callback)
-
end
-
-
23
def remove_duplicates(callback)
-
141
@callbacks = nil
-
394
@chain.delete_if { |c| callback.duplicates?(c) }
-
end
-
-
23
def default_terminator
-
66
Proc.new do |target, result_lambda|
-
terminate = true
-
catch(:abort) do
-
result_lambda.call
-
terminate = false
-
end
-
terminate
-
end
-
end
-
end
-
-
23
module ClassMethods
-
23
def normalize_callback_params(filters, block) # :nodoc:
-
144
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
-
144
options = filters.extract_options!
-
144
filters.unshift(block) if block
-
144
[type, filters, options.dup]
-
end
-
-
# This is used internally to append, prepend and skip callbacks to the
-
# CallbackChain.
-
23
def __update_callbacks(name) #:nodoc:
-
144
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
-
144
chain = target.get_callbacks name
-
144
yield target, chain.dup
-
end
-
end
-
-
# Install a callback for the given event.
-
#
-
# set_callback :save, :before, :before_method
-
# set_callback :save, :after, :after_method, if: :condition
-
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
-
#
-
# The second argument indicates whether the callback is to be run +:before+,
-
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
-
# means the first example above can also be written as:
-
#
-
# set_callback :save, :before_method
-
#
-
# The callback can be specified as a symbol naming an instance method; as a
-
# proc, lambda, or block; or as an object that responds to a certain method
-
# determined by the <tt>:scope</tt> argument to +define_callbacks+.
-
#
-
# If a proc, lambda, or block is given, its body is evaluated in the context
-
# of the current object. It can also optionally accept the current object as
-
# an argument.
-
#
-
# Before and around callbacks are called in the order that they are set;
-
# after callbacks are called in the reverse order.
-
#
-
# Around callbacks can access the return value from the event, if it
-
# wasn't halted, from the +yield+ call.
-
#
-
# ===== Options
-
#
-
# * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
-
# method or a proc; the callback will be called only when they all return
-
# a true value.
-
#
-
# If a proc is given, its body is evaluated in the context of the
-
# current object. It can also optionally accept the current object as
-
# an argument.
-
# * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
-
# instance method or a proc; the callback will be called only when they
-
# all return a false value.
-
#
-
# If a proc is given, its body is evaluated in the context of the
-
# current object. It can also optionally accept the current object as
-
# an argument.
-
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
-
# existing chain rather than appended.
-
23
def set_callback(name, *filter_list, &block)
-
130
type, filters, options = normalize_callback_params(filter_list, block)
-
-
130
self_chain = get_callbacks name
-
130
mapped = filters.map do |filter|
-
141
Callback.build(self_chain, filter, type, options)
-
end
-
-
130
__update_callbacks(name) do |target, chain|
-
130
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
-
130
target.set_callbacks name, chain
-
end
-
end
-
-
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
-
# <tt>:unless</tt> options may be passed in order to control when the
-
# callback is skipped.
-
#
-
# class Writer < Person
-
# skip_callback :validate, :before, :check_membership, if: -> { age > 18 }
-
# end
-
#
-
# An <tt>ArgumentError</tt> will be raised if the callback has not
-
# already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
-
23
def skip_callback(name, *filter_list, &block)
-
14
type, filters, options = normalize_callback_params(filter_list, block)
-
-
14
options[:raise] = true unless options.key?(:raise)
-
-
14
__update_callbacks(name) do |target, chain|
-
14
filters.each do |filter|
-
52
callback = chain.find { |c| c.matches?(type, filter) }
-
-
14
if !callback && options[:raise]
-
raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
-
end
-
-
14
if callback && (options.key?(:if) || options.key?(:unless))
-
13
new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
-
13
chain.insert(chain.index(callback), new_callback)
-
end
-
-
14
chain.delete(callback)
-
end
-
14
target.set_callbacks name, chain
-
end
-
end
-
-
# Remove all set callbacks for the given event.
-
23
def reset_callbacks(name)
-
1
callbacks = get_callbacks name
-
-
1
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
-
chain = target.get_callbacks(name).dup
-
callbacks.each { |c| chain.delete(c) }
-
target.set_callbacks name, chain
-
end
-
-
1
set_callbacks(name, callbacks.dup.clear)
-
end
-
-
# Define sets of events in the object life cycle that support callbacks.
-
#
-
# define_callbacks :validate
-
# define_callbacks :initialize, :save, :destroy
-
#
-
# ===== Options
-
#
-
# * <tt>:terminator</tt> - Determines when a before filter will halt the
-
# callback chain, preventing following before and around callbacks from
-
# being called and the event from being triggered.
-
# This should be a lambda to be executed.
-
# The current object and the result lambda of the callback will be provided
-
# to the terminator lambda.
-
#
-
# define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
-
#
-
# In this example, if any before validate callbacks returns +false+,
-
# any successive before and around callback is not executed.
-
#
-
# The default terminator halts the chain when a callback throws +:abort+.
-
#
-
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
-
# callbacks should be terminated by the <tt>:terminator</tt> option. By
-
# default after callbacks are executed no matter if callback chain was
-
# terminated or not. This option has no effect if <tt>:terminator</tt>
-
# option is set to +nil+.
-
#
-
# * <tt>:scope</tt> - Indicates which methods should be executed when an
-
# object is used as a callback.
-
#
-
# class Audit
-
# def before(caller)
-
# puts 'Audit: before is called'
-
# end
-
#
-
# def before_save(caller)
-
# puts 'Audit: before_save is called'
-
# end
-
# end
-
#
-
# class Account
-
# include ActiveSupport::Callbacks
-
#
-
# define_callbacks :save
-
# set_callback :save, :before, Audit.new
-
#
-
# def save
-
# run_callbacks :save do
-
# puts 'save in main'
-
# end
-
# end
-
# end
-
#
-
# In the above case whenever you save an account the method
-
# <tt>Audit#before</tt> will be called. On the other hand
-
#
-
# define_callbacks :save, scope: [:kind, :name]
-
#
-
# would trigger <tt>Audit#before_save</tt> instead. That's constructed
-
# by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
-
# case "kind" is "before" and "name" is "save". In this context +:kind+
-
# and +:name+ have special meanings: +:kind+ refers to the kind of
-
# callback (before/after/around) and +:name+ refers to the method on
-
# which callbacks are being defined.
-
#
-
# A declaration like
-
#
-
# define_callbacks :save, scope: [:name]
-
#
-
# would call <tt>Audit#save</tt>.
-
#
-
# ===== Notes
-
#
-
# +names+ passed to +define_callbacks+ must not end with
-
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
-
#
-
# Calling +define_callbacks+ multiple times with the same +names+ will
-
# overwrite previous callbacks registered with +set_callback+.
-
23
def define_callbacks(*names)
-
43
options = names.extract_options!
-
-
43
names.each do |name|
-
66
name = name.to_sym
-
-
66
([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
-
66
target.set_callbacks name, CallbackChain.new(name, options)
-
end
-
-
66
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def _run_#{name}_callbacks(&block)
-
run_callbacks #{name.inspect}, &block
-
end
-
-
def self._#{name}_callbacks
-
get_callbacks(#{name.inspect})
-
end
-
-
def self._#{name}_callbacks=(value)
-
set_callbacks(#{name.inspect}, value)
-
end
-
-
def _#{name}_callbacks
-
__callbacks[#{name.inspect}]
-
end
-
RUBY
-
end
-
end
-
-
23
protected
-
23
def get_callbacks(name) # :nodoc:
-
275
__callbacks[name.to_sym]
-
end
-
-
23
if Module.instance_method(:method_defined?).arity == 1 # Ruby 2.5 and older
-
23
def set_callbacks(name, callbacks) # :nodoc:
-
211
self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
-
end
-
else # Ruby 2.6 and newer
-
def set_callbacks(name, callbacks) # :nodoc:
-
unless singleton_class.method_defined?(:__callbacks, false)
-
self.__callbacks = __callbacks.dup
-
end
-
self.__callbacks[name.to_sym] = callbacks
-
self.__callbacks
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
module ActiveSupport
-
# A typical module looks like this:
-
#
-
# module M
-
# def self.included(base)
-
# base.extend ClassMethods
-
# base.class_eval do
-
# scope :disabled, -> { where(disabled: true) }
-
# end
-
# end
-
#
-
# module ClassMethods
-
# ...
-
# end
-
# end
-
#
-
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
-
# written as:
-
#
-
# require "active_support/concern"
-
#
-
# module M
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# scope :disabled, -> { where(disabled: true) }
-
# end
-
#
-
# class_methods do
-
# ...
-
# end
-
# end
-
#
-
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
-
# and a +Bar+ module which depends on the former, we would typically write the
-
# following:
-
#
-
# module Foo
-
# def self.included(base)
-
# base.class_eval do
-
# def self.method_injected_by_foo
-
# ...
-
# end
-
# end
-
# end
-
# end
-
#
-
# module Bar
-
# def self.included(base)
-
# base.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Foo # We need to include this dependency for Bar
-
# include Bar # Bar is the module that Host really needs
-
# end
-
#
-
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
-
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
-
#
-
# module Bar
-
# include Foo
-
# def self.included(base)
-
# base.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Bar
-
# end
-
#
-
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
-
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
-
# module dependencies are properly resolved:
-
#
-
# require "active_support/concern"
-
#
-
# module Foo
-
# extend ActiveSupport::Concern
-
# included do
-
# def self.method_injected_by_foo
-
# ...
-
# end
-
# end
-
# end
-
#
-
# module Bar
-
# extend ActiveSupport::Concern
-
# include Foo
-
#
-
# included do
-
# self.method_injected_by_foo
-
# end
-
# end
-
#
-
# class Host
-
# include Bar # It works, now Bar takes care of its dependencies
-
# end
-
#
-
# === Prepending concerns
-
#
-
# Just like `include`, concerns also support `prepend` with a corresponding
-
# `prepended do` callback. `module ClassMethods` or `class_methods do` are
-
# prepended as well.
-
#
-
# `prepend` is also used for any dependencies.
-
24
module Concern
-
24
class MultipleIncludedBlocks < StandardError #:nodoc:
-
24
def initialize
-
super "Cannot define multiple 'included' blocks for a Concern"
-
end
-
end
-
-
24
class MultiplePrependBlocks < StandardError #:nodoc:
-
24
def initialize
-
super "Cannot define multiple 'prepended' blocks for a Concern"
-
end
-
end
-
-
24
def self.extended(base) #:nodoc:
-
149
base.instance_variable_set(:@_dependencies, [])
-
end
-
-
24
def append_features(base) #:nodoc:
-
124
if base.instance_variable_defined?(:@_dependencies)
-
3
base.instance_variable_get(:@_dependencies) << self
-
3
false
-
else
-
121
return false if base < self
-
120
@_dependencies.each { |dep| base.include(dep) }
-
120
super
-
120
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
-
120
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
-
end
-
end
-
-
24
def prepend_features(base) #:nodoc:
-
1
if base.instance_variable_defined?(:@_dependencies)
-
base.instance_variable_get(:@_dependencies).unshift self
-
false
-
else
-
1
return false if base < self
-
1
@_dependencies.each { |dep| base.prepend(dep) }
-
1
super
-
1
base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
-
1
base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
-
end
-
end
-
-
# Evaluate given block in context of base class,
-
# so that you can write class macros here.
-
# When you define more than one +included+ block, it raises an exception.
-
24
def included(base = nil, &block)
-
245
if base.nil?
-
121
if instance_variable_defined?(:@_included_block)
-
if @_included_block.source_location != block.source_location
-
raise MultipleIncludedBlocks
-
end
-
else
-
121
@_included_block = block
-
end
-
else
-
124
super
-
end
-
end
-
-
# Evaluate given block in context of base class,
-
# so that you can write class macros here.
-
# When you define more than one +prepended+ block, it raises an exception.
-
24
def prepended(base = nil, &block)
-
2
if base.nil?
-
1
if instance_variable_defined?(:@_prepended_block)
-
if @_prepended_block.source_location != block.source_location
-
raise MultiplePrependBlocks
-
end
-
else
-
1
@_prepended_block = block
-
end
-
else
-
1
super
-
end
-
end
-
-
# Define class methods from given block.
-
# You can define private class methods as well.
-
#
-
# module Example
-
# extend ActiveSupport::Concern
-
#
-
# class_methods do
-
# def foo; puts 'foo'; end
-
#
-
# private
-
# def bar; puts 'bar'; end
-
# end
-
# end
-
#
-
# class Buzz
-
# include Example
-
# end
-
#
-
# Buzz.foo # => "foo"
-
# Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
-
24
def class_methods(&class_methods_module_definition)
-
5
mod = const_defined?(:ClassMethods, false) ?
-
const_get(:ClassMethods) :
-
const_set(:ClassMethods, Module.new)
-
-
5
mod.module_eval(&class_methods_module_definition)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "monitor"
-
-
1
module ActiveSupport
-
1
module Concurrency
-
# A monitor that will permit dependency loading while blocked waiting for
-
# the lock.
-
1
class LoadInterlockAwareMonitor < Monitor
-
1
EXCEPTION_NEVER = { Exception => :never }.freeze
-
1
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze
-
1
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
-
-
# Enters an exclusive section, but allows dependency loading while blocked
-
1
def mon_enter
-
mon_try_enter ||
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
-
end
-
-
1
def synchronize
-
Thread.handle_interrupt(EXCEPTION_NEVER) do
-
mon_enter
-
-
begin
-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do
-
yield
-
end
-
ensure
-
mon_exit
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "thread"
-
3
require "monitor"
-
-
3
module ActiveSupport
-
3
module Concurrency
-
# A share/exclusive lock, otherwise known as a read/write lock.
-
#
-
# https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
-
3
class ShareLock
-
3
include MonitorMixin
-
-
# We track Thread objects, instead of just using counters, because
-
# we need exclusive locks to be reentrant, and we need to be able
-
# to upgrade share locks to exclusive.
-
-
3
def raw_state # :nodoc:
-
synchronize do
-
threads = @sleeping.keys | @sharing.keys | @waiting.keys
-
threads |= [@exclusive_thread] if @exclusive_thread
-
-
data = {}
-
-
threads.each do |thread|
-
purpose, compatible = @waiting[thread]
-
-
data[thread] = {
-
thread: thread,
-
sharing: @sharing[thread],
-
exclusive: @exclusive_thread == thread,
-
purpose: purpose,
-
compatible: compatible,
-
waiting: !!@waiting[thread],
-
sleeper: @sleeping[thread],
-
}
-
end
-
-
# NB: Yields while holding our *internal* synchronize lock,
-
# which is supposed to be used only for a few instructions at
-
# a time. This allows the caller to inspect additional state
-
# without things changing out from underneath, but would have
-
# disastrous effects upon normal operation. Fortunately, this
-
# method is only intended to be called when things have
-
# already gone wrong.
-
yield data
-
end
-
end
-
-
3
def initialize
-
3
super()
-
-
3
@cv = new_cond
-
-
3
@sharing = Hash.new(0)
-
3
@waiting = {}
-
3
@sleeping = {}
-
3
@exclusive_thread = nil
-
3
@exclusive_depth = 0
-
end
-
-
# Returns false if +no_wait+ is set and the lock is not
-
# immediately available. Otherwise, returns true after the lock
-
# has been acquired.
-
#
-
# +purpose+ and +compatible+ work together; while this thread is
-
# waiting for the exclusive lock, it will yield its share (if any)
-
# to any other attempt whose +purpose+ appears in this attempt's
-
# +compatible+ list. This allows a "loose" upgrade, which, being
-
# less strict, prevents some classes of deadlocks.
-
#
-
# For many resources, loose upgrades are sufficient: if a thread
-
# is awaiting a lock, it is not running any other code. With
-
# +purpose+ matching, it is possible to yield only to other
-
# threads whose activity will not interfere.
-
3
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
-
synchronize do
-
unless @exclusive_thread == Thread.current
-
if busy_for_exclusive?(purpose)
-
return false if no_wait
-
-
yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
-
wait_for(:start_exclusive) { busy_for_exclusive?(purpose) }
-
end
-
end
-
@exclusive_thread = Thread.current
-
end
-
@exclusive_depth += 1
-
-
true
-
end
-
end
-
-
# Relinquish the exclusive lock. Must only be called by the thread
-
# that called start_exclusive (and currently holds the lock).
-
3
def stop_exclusive(compatible: [])
-
synchronize do
-
raise "invalid unlock" if @exclusive_thread != Thread.current
-
-
@exclusive_depth -= 1
-
if @exclusive_depth == 0
-
@exclusive_thread = nil
-
-
if eligible_waiters?(compatible)
-
yield_shares(compatible: compatible, block_share: true) do
-
wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) }
-
end
-
end
-
@cv.broadcast
-
end
-
end
-
end
-
-
3
def start_sharing
-
synchronize do
-
if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
-
# We already hold a lock; nothing to wait for
-
elsif @waiting[Thread.current]
-
# We're nested inside a +yield_shares+ call: we'll resume as
-
# soon as there isn't an exclusive lock in our way
-
wait_for(:start_sharing) { @exclusive_thread }
-
else
-
# This is an initial / outermost share call: any outstanding
-
# requests for an exclusive lock get to go first
-
wait_for(:start_sharing) { busy_for_sharing?(false) }
-
end
-
@sharing[Thread.current] += 1
-
end
-
end
-
-
3
def stop_sharing
-
synchronize do
-
if @sharing[Thread.current] > 1
-
@sharing[Thread.current] -= 1
-
else
-
@sharing.delete Thread.current
-
@cv.broadcast
-
end
-
end
-
end
-
-
# Execute the supplied block while holding the Exclusive lock. If
-
# +no_wait+ is set and the lock is not immediately available,
-
# returns +nil+ without yielding. Otherwise, returns the result of
-
# the block.
-
#
-
# See +start_exclusive+ for other options.
-
3
def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
-
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
-
begin
-
yield
-
ensure
-
stop_exclusive(compatible: after_compatible)
-
end
-
end
-
end
-
-
# Execute the supplied block while holding the Share lock.
-
3
def sharing
-
start_sharing
-
begin
-
yield
-
ensure
-
stop_sharing
-
end
-
end
-
-
# Temporarily give up all held Share locks while executing the
-
# supplied block, allowing any +compatible+ exclusive lock request
-
# to proceed.
-
3
def yield_shares(purpose: nil, compatible: [], block_share: false)
-
loose_shares = previous_wait = nil
-
synchronize do
-
if loose_shares = @sharing.delete(Thread.current)
-
if previous_wait = @waiting[Thread.current]
-
purpose = nil unless purpose == previous_wait[0]
-
compatible &= previous_wait[1]
-
end
-
compatible |= [false] unless block_share
-
@waiting[Thread.current] = [purpose, compatible]
-
end
-
-
@cv.broadcast
-
end
-
-
begin
-
yield
-
ensure
-
synchronize do
-
wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current }
-
-
if previous_wait
-
@waiting[Thread.current] = previous_wait
-
else
-
@waiting.delete Thread.current
-
end
-
@sharing[Thread.current] = loose_shares if loose_shares
-
end
-
end
-
end
-
-
3
private
-
# Must be called within synchronize
-
3
def busy_for_exclusive?(purpose)
-
busy_for_sharing?(purpose) ||
-
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
-
end
-
-
3
def busy_for_sharing?(purpose)
-
(@exclusive_thread && @exclusive_thread != Thread.current) ||
-
@waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
-
end
-
-
3
def eligible_waiters?(compatible)
-
@waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
-
end
-
-
3
def wait_for(method)
-
@sleeping[Thread.current] = method
-
@cv.wait_while { yield }
-
ensure
-
@sleeping.delete Thread.current
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/concern"
-
1
require "active_support/ordered_options"
-
-
1
module ActiveSupport
-
# Configurable provides a <tt>config</tt> method to store and retrieve
-
# configuration options as an <tt>OrderedOptions</tt>.
-
1
module Configurable
-
1
extend ActiveSupport::Concern
-
-
1
class Configuration < ActiveSupport::InheritableOptions
-
1
def compile_methods!
-
self.class.compile_methods!(keys)
-
end
-
-
# Compiles reader methods so we don't have to go through method_missing.
-
1
def self.compile_methods!(keys)
-
keys.reject { |m| method_defined?(m) }.each do |key|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{key}; _get(#{key.inspect}); end
-
RUBY
-
end
-
end
-
end
-
-
1
module ClassMethods
-
1
def config
-
@_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
-
superclass.config.inheritable_copy
-
else
-
# create a new "anonymous" class that will host the compiled reader methods
-
Class.new(Configuration).new
-
end
-
end
-
-
1
def configure
-
yield config
-
end
-
-
# Allows you to add shortcut so that you don't have to refer to attribute
-
# through config. Also look at the example for config to contrast.
-
#
-
# Defines both class and instance config accessors.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access
-
# end
-
#
-
# User.allowed_access # => nil
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# user = User.new
-
# user.allowed_access # => false
-
# user.allowed_access = true
-
# user.allowed_access # => true
-
#
-
# User.allowed_access # => false
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :"1_Badname"
-
# end
-
# # => NameError: invalid config attribute name
-
#
-
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access, instance_reader: false, instance_writer: false
-
# end
-
#
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# User.new.allowed_access = true # => NoMethodError
-
# User.new.allowed_access # => NoMethodError
-
#
-
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :allowed_access, instance_accessor: false
-
# end
-
#
-
# User.allowed_access = false
-
# User.allowed_access # => false
-
#
-
# User.new.allowed_access = true # => NoMethodError
-
# User.new.allowed_access # => NoMethodError
-
#
-
# Also you can pass a block to set up the attribute with a default value.
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# config_accessor :hair_colors do
-
# [:brown, :black, :blonde, :red]
-
# end
-
# end
-
#
-
# User.hair_colors # => [:brown, :black, :blonde, :red]
-
1
def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :doc:
-
3
names.each do |name|
-
3
raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name)
-
-
3
reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
-
3
writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
-
-
3
singleton_class.class_eval reader, __FILE__, reader_line
-
3
singleton_class.class_eval writer, __FILE__, writer_line
-
-
3
if instance_accessor
-
2
class_eval reader, __FILE__, reader_line if instance_reader
-
2
class_eval writer, __FILE__, writer_line if instance_writer
-
end
-
3
send("#{name}=", yield) if block_given?
-
end
-
end
-
1
private :config_accessor
-
end
-
-
# Reads and writes attributes from a configuration <tt>OrderedOptions</tt>.
-
#
-
# require "active_support/configurable"
-
#
-
# class User
-
# include ActiveSupport::Configurable
-
# end
-
#
-
# user = User.new
-
#
-
# user.config.allowed_access = true
-
# user.config.level = 1
-
#
-
# user.config.allowed_access # => true
-
# user.config.level # => 1
-
1
def config
-
@_config ||= self.class.config.inheritable_copy
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
# Reads a YAML configuration file, evaluating any ERB, then
-
# parsing the resulting YAML.
-
#
-
# Warns in case of YAML confusing characters, like invisible
-
# non-breaking spaces.
-
class ConfigurationFile # :nodoc:
-
class FormatError < StandardError; end
-
-
def initialize(content_path)
-
@content_path = content_path.to_s
-
@content = read content_path
-
end
-
-
def self.parse(content_path, **options)
-
new(content_path).parse(**options)
-
end
-
-
def parse(context: nil, **options)
-
YAML.load(render(context), **options) || {}
-
rescue Psych::SyntaxError => error
-
raise "YAML syntax error occurred while parsing #{@content_path}. " \
-
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
-
"Error: #{error.message}"
-
end
-
-
private
-
def read(content_path)
-
require "yaml"
-
require "erb"
-
-
File.read(content_path).tap do |content|
-
if content.include?("\u00A0")
-
warn "File contains invisible non-breaking spaces, you may want to remove those"
-
end
-
end
-
end
-
-
def render(context)
-
erb = ERB.new(@content).tap { |e| e.filename = @content_path }
-
context ? erb.result(context) : erb.result
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
-
require path
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/array/wrap"
-
1
require "active_support/core_ext/array/access"
-
1
require "active_support/core_ext/array/conversions"
-
1
require "active_support/core_ext/array/extract"
-
1
require "active_support/core_ext/array/extract_options"
-
1
require "active_support/core_ext/array/grouping"
-
1
require "active_support/core_ext/array/inquiry"
-
# frozen_string_literal: true
-
-
1
class Array
-
# Returns the tail of the array from +position+.
-
#
-
# %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
-
# %w( a b c d ).from(2) # => ["c", "d"]
-
# %w( a b c d ).from(10) # => []
-
# %w().from(0) # => []
-
# %w( a b c d ).from(-2) # => ["c", "d"]
-
# %w( a b c ).from(-10) # => []
-
1
def from(position)
-
self[position, length] || []
-
end
-
-
# Returns the beginning of the array up to +position+.
-
#
-
# %w( a b c d ).to(0) # => ["a"]
-
# %w( a b c d ).to(2) # => ["a", "b", "c"]
-
# %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
-
# %w().to(0) # => []
-
# %w( a b c d ).to(-2) # => ["a", "b", "c"]
-
# %w( a b c ).to(-10) # => []
-
1
def to(position)
-
if position >= 0
-
take position + 1
-
else
-
self[0..position]
-
end
-
end
-
-
# Returns a new array that includes the passed elements.
-
#
-
# [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ]
-
# [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]
-
1
def including(*elements)
-
self + elements.flatten(1)
-
end
-
-
# Returns a copy of the Array excluding the specified elements.
-
#
-
# ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
-
# [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ]
-
#
-
# Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt>
-
# instead of <tt>Array#reject</tt> for performance reasons.
-
1
def excluding(*elements)
-
self - elements.flatten(1)
-
end
-
-
# Alias for #excluding.
-
1
def without(*elements)
-
excluding(*elements)
-
end
-
-
# Equal to <tt>self[1]</tt>.
-
#
-
# %w( a b c d e ).second # => "b"
-
1
def second
-
self[1]
-
end
-
-
# Equal to <tt>self[2]</tt>.
-
#
-
# %w( a b c d e ).third # => "c"
-
1
def third
-
self[2]
-
end
-
-
# Equal to <tt>self[3]</tt>.
-
#
-
# %w( a b c d e ).fourth # => "d"
-
1
def fourth
-
self[3]
-
end
-
-
# Equal to <tt>self[4]</tt>.
-
#
-
# %w( a b c d e ).fifth # => "e"
-
1
def fifth
-
self[4]
-
end
-
-
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
-
#
-
# (1..42).to_a.forty_two # => 42
-
1
def forty_two
-
self[41]
-
end
-
-
# Equal to <tt>self[-3]</tt>.
-
#
-
# %w( a b c d e ).third_to_last # => "c"
-
1
def third_to_last
-
self[-3]
-
end
-
-
# Equal to <tt>self[-2]</tt>.
-
#
-
# %w( a b c d e ).second_to_last # => "d"
-
1
def second_to_last
-
self[-2]
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/xml_mini"
-
23
require "active_support/core_ext/hash/keys"
-
23
require "active_support/core_ext/string/inflections"
-
23
require "active_support/core_ext/object/to_param"
-
23
require "active_support/core_ext/object/to_query"
-
-
23
class Array
-
# Converts the array to a comma-separated sentence where the last element is
-
# joined by the connector word.
-
#
-
# You can pass the following options to change the default behavior. If you
-
# pass an option key that doesn't exist in the list below, it will raise an
-
# <tt>ArgumentError</tt>.
-
#
-
# ==== Options
-
#
-
# * <tt>:words_connector</tt> - The sign or word used to join the elements
-
# in arrays with two or more elements (default: ", ").
-
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements
-
# in arrays with two elements (default: " and ").
-
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element
-
# in arrays with three or more elements (default: ", and ").
-
# * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
-
# the connector options defined on the 'support.array' namespace in the
-
# corresponding dictionary file.
-
#
-
# ==== Examples
-
#
-
# [].to_sentence # => ""
-
# ['one'].to_sentence # => "one"
-
# ['one', 'two'].to_sentence # => "one and two"
-
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
-
#
-
# ['one', 'two'].to_sentence(passing: 'invalid option')
-
# # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale
-
#
-
# ['one', 'two'].to_sentence(two_words_connector: '-')
-
# # => "one-two"
-
#
-
# ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
-
# # => "one or two or at least three"
-
#
-
# Using <tt>:locale</tt> option:
-
#
-
# # Given this locale dictionary:
-
# #
-
# # es:
-
# # support:
-
# # array:
-
# # words_connector: " o "
-
# # two_words_connector: " y "
-
# # last_word_connector: " o al menos "
-
#
-
# ['uno', 'dos'].to_sentence(locale: :es)
-
# # => "uno y dos"
-
#
-
# ['uno', 'dos', 'tres'].to_sentence(locale: :es)
-
# # => "uno o dos o al menos tres"
-
23
def to_sentence(options = {})
-
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
-
-
default_connectors = {
-
words_connector: ", ",
-
two_words_connector: " and ",
-
last_word_connector: ", and "
-
}
-
if defined?(I18n)
-
i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
-
default_connectors.merge!(i18n_connectors)
-
end
-
options = default_connectors.merge!(options)
-
-
case length
-
when 0
-
+""
-
when 1
-
+"#{self[0]}"
-
when 2
-
+"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
-
else
-
+"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
-
end
-
end
-
-
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
-
# comma separated id list if <tt>:db</tt> argument is given as the format.
-
#
-
# Blog.all.to_formatted_s(:db) # => "1,2,3"
-
# Blog.none.to_formatted_s(:db) # => "null"
-
# [1,2].to_formatted_s # => "[1, 2]"
-
23
def to_formatted_s(format = :default)
-
case format
-
when :db
-
if empty?
-
"null"
-
else
-
collect(&:id).join(",")
-
end
-
else
-
to_default_s
-
end
-
end
-
23
alias_method :to_default_s, :to_s
-
23
alias_method :to_s, :to_formatted_s
-
-
# Returns a string that represents the array in XML by invoking +to_xml+
-
# on each element. Active Record collections delegate their representation
-
# in XML to this method.
-
#
-
# All elements are expected to respond to +to_xml+, if any of them does
-
# not then an exception is raised.
-
#
-
# The root node reflects the class name of the first element in plural
-
# if all elements belong to the same type and that's not Hash:
-
#
-
# customer.projects.to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <projects type="array">
-
# <project>
-
# <amount type="decimal">20000.0</amount>
-
# <customer-id type="integer">1567</customer-id>
-
# <deal-date type="date">2008-04-09</deal-date>
-
# ...
-
# </project>
-
# <project>
-
# <amount type="decimal">57230.0</amount>
-
# <customer-id type="integer">1567</customer-id>
-
# <deal-date type="date">2008-04-15</deal-date>
-
# ...
-
# </project>
-
# </projects>
-
#
-
# Otherwise the root element is "objects":
-
#
-
# [{ foo: 1, bar: 2}, { baz: 3}].to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <objects type="array">
-
# <object>
-
# <bar type="integer">2</bar>
-
# <foo type="integer">1</foo>
-
# </object>
-
# <object>
-
# <baz type="integer">3</baz>
-
# </object>
-
# </objects>
-
#
-
# If the collection is empty the root element is "nil-classes" by default:
-
#
-
# [].to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <nil-classes type="array"/>
-
#
-
# To ensure a meaningful root element use the <tt>:root</tt> option:
-
#
-
# customer_with_no_projects.projects.to_xml(root: 'projects')
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <projects type="array"/>
-
#
-
# By default name of the node for the children of root is <tt>root.singularize</tt>.
-
# You can change it with the <tt>:children</tt> option.
-
#
-
# The +options+ hash is passed downwards:
-
#
-
# Message.all.to_xml(skip_types: true)
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <messages>
-
# <message>
-
# <created-at>2008-03-07T09:58:18+01:00</created-at>
-
# <id>1</id>
-
# <name>1</name>
-
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
-
# <user-id>1</user-id>
-
# </message>
-
# </messages>
-
#
-
23
def to_xml(options = {})
-
require "active_support/builder" unless defined?(Builder::XmlMarkup)
-
-
options = options.dup
-
options[:indent] ||= 2
-
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
-
options[:root] ||= \
-
if first.class != Hash && all? { |e| e.is_a?(first.class) }
-
underscored = ActiveSupport::Inflector.underscore(first.class.name)
-
ActiveSupport::Inflector.pluralize(underscored).tr("/", "_")
-
else
-
"objects"
-
end
-
-
builder = options[:builder]
-
builder.instruct! unless options.delete(:skip_instruct)
-
-
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
-
children = options.delete(:children) || root.singularize
-
attributes = options[:skip_types] ? {} : { type: "array" }
-
-
if empty?
-
builder.tag!(root, attributes)
-
else
-
builder.tag!(root, attributes) do
-
each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
-
yield builder if block_given?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Array
-
# Removes and returns the elements for which the block returns a true value.
-
# If no block is given, an Enumerator is returned instead.
-
#
-
# numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-
# odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
-
# numbers # => [0, 2, 4, 6, 8]
-
1
def extract!
-
return to_enum(:extract!) { size } unless block_given?
-
-
extracted_elements = []
-
-
reject! do |element|
-
extracted_elements << element if yield(element)
-
end
-
-
extracted_elements
-
end
-
end
-
# frozen_string_literal: true
-
-
24
class Hash
-
# By default, only instances of Hash itself are extractable.
-
# Subclasses of Hash may implement this method and return
-
# true to declare themselves as extractable. If a Hash
-
# is extractable, Array#extract_options! pops it from
-
# the Array when it is the last element of the Array.
-
24
def extractable_options?
-
45
instance_of?(Hash)
-
end
-
end
-
-
24
class Array
-
# Extracts options from a set of arguments. Removes and returns the last
-
# element in the array if it's a hash, otherwise returns a blank hash.
-
#
-
# def options(*args)
-
# args.extract_options!
-
# end
-
#
-
# options(1, 2) # => {}
-
# options(1, 2, a: :b) # => {:a=>:b}
-
24
def extract_options!
-
191
if last.is_a?(Hash) && last.extractable_options?
-
45
pop
-
else
-
146
{}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Array
-
# Splits or iterates over the array in groups of size +number+,
-
# padding any remaining slots with +fill_with+ unless it is +false+.
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
-
# ["1", "2", "3"]
-
# ["4", "5", "6"]
-
# ["7", "8", "9"]
-
# ["10", nil, nil]
-
#
-
# %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group}
-
# ["1", "2"]
-
# ["3", "4"]
-
# ["5", " "]
-
#
-
# %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
-
# ["1", "2"]
-
# ["3", "4"]
-
# ["5"]
-
1
def in_groups_of(number, fill_with = nil)
-
if number.to_i <= 0
-
raise ArgumentError,
-
"Group size must be a positive integer, was #{number.inspect}"
-
end
-
-
if fill_with == false
-
collection = self
-
else
-
# size % number gives how many extra we have;
-
# subtracting from number gives how many to add;
-
# modulo number ensures we don't add group of just fill.
-
padding = (number - size % number) % number
-
collection = dup.concat(Array.new(padding, fill_with))
-
end
-
-
if block_given?
-
collection.each_slice(number) { |slice| yield(slice) }
-
else
-
collection.each_slice(number).to_a
-
end
-
end
-
-
# Splits or iterates over the array in +number+ of groups, padding any
-
# remaining slots with +fill_with+ unless it is +false+.
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
-
# ["1", "2", "3", "4"]
-
# ["5", "6", "7", nil]
-
# ["8", "9", "10", nil]
-
#
-
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group}
-
# ["1", "2", "3", "4"]
-
# ["5", "6", "7", " "]
-
# ["8", "9", "10", " "]
-
#
-
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
-
# ["1", "2", "3"]
-
# ["4", "5"]
-
# ["6", "7"]
-
1
def in_groups(number, fill_with = nil)
-
# size.div number gives minor group size;
-
# size % number gives how many objects need extra accommodation;
-
# each group hold either division or division + 1 items.
-
division = size.div number
-
modulo = size % number
-
-
# create a new array avoiding dup
-
groups = []
-
start = 0
-
-
number.times do |index|
-
length = division + (modulo > 0 && modulo > index ? 1 : 0)
-
groups << last_group = slice(start, length)
-
last_group << fill_with if fill_with != false &&
-
modulo > 0 && length == division
-
start += length
-
end
-
-
if block_given?
-
groups.each { |g| yield(g) }
-
else
-
groups
-
end
-
end
-
-
# Divides the array into one or more subarrays based on a delimiting +value+
-
# or the result of an optional block.
-
#
-
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
-
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
-
1
def split(value = nil)
-
arr = dup
-
result = []
-
if block_given?
-
while (idx = arr.index { |i| yield i })
-
result << arr.shift(idx)
-
arr.shift
-
end
-
else
-
while (idx = arr.index(value))
-
result << arr.shift(idx)
-
arr.shift
-
end
-
end
-
result << arr
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/array_inquirer"
-
-
1
class Array
-
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
-
# to check its string-like contents.
-
#
-
# pets = [:cat, :dog].inquiry
-
#
-
# pets.cat? # => true
-
# pets.ferret? # => false
-
#
-
# pets.any?(:cat, :ferret) # => true
-
# pets.any?(:ferret, :alligator) # => false
-
1
def inquiry
-
ActiveSupport::ArrayInquirer.new(self)
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/deprecation"
-
-
ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Array#append and Array#prepend natively, so requiring active_support/core_ext/array/prepend_and_append is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
-
# frozen_string_literal: true
-
-
13
class Array
-
# Wraps its argument in an array unless it is already an array (or array-like).
-
#
-
# Specifically:
-
#
-
# * If the argument is +nil+ an empty array is returned.
-
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
-
# * Otherwise, returns an array with the argument as its single element.
-
#
-
# Array.wrap(nil) # => []
-
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
-
# Array.wrap(0) # => [0]
-
#
-
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
-
#
-
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
-
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
-
# an array with the argument as its single element right away.
-
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
-
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
-
# * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+
-
# it returns an array with the argument as its single element.
-
#
-
# The last point is easily explained with some enumerables:
-
#
-
# Array(foo: :bar) # => [[:foo, :bar]]
-
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
-
#
-
# There's also a related idiom that uses the splat operator:
-
#
-
# [*object]
-
#
-
# which returns <tt>[]</tt> for +nil+, but calls to <tt>Array(object)</tt> otherwise.
-
#
-
# The differences with <tt>Kernel#Array</tt> explained above
-
# apply to the rest of <tt>object</tt>s.
-
13
def self.wrap(object)
-
if object.nil?
-
[]
-
elsif object.respond_to?(:to_ary)
-
object.to_ary || [object]
-
else
-
[object]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "benchmark"
-
-
2
class << Benchmark
-
# Benchmark realtime in milliseconds.
-
#
-
# Benchmark.realtime { User.all }
-
# # => 8.0e-05
-
#
-
# Benchmark.ms { User.all }
-
# # => 0.074
-
2
def ms(&block)
-
1000 * realtime(&block)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/big_decimal/conversions"
-
# frozen_string_literal: true
-
-
2
require "bigdecimal"
-
2
require "bigdecimal/util"
-
-
2
module ActiveSupport
-
2
module BigDecimalWithDefaultFormat #:nodoc:
-
2
def to_s(format = "F")
-
1
super(format)
-
end
-
end
-
end
-
-
2
BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat)
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/class/attribute"
-
1
require "active_support/core_ext/class/subclasses"
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/module/redefine_method"
-
-
23
class Class
-
# Declare a class-level attribute whose value is inheritable by subclasses.
-
# Subclasses can change their own value and it will not impact parent class.
-
#
-
# ==== Options
-
#
-
# * <tt>:instance_reader</tt> - Sets the instance reader method (defaults to true).
-
# * <tt>:instance_writer</tt> - Sets the instance writer method (defaults to true).
-
# * <tt>:instance_accessor</tt> - Sets both instance methods (defaults to true).
-
# * <tt>:instance_predicate</tt> - Sets a predicate method (defaults to true).
-
# * <tt>:default</tt> - Sets a default value for the attribute (defaults to nil).
-
#
-
# ==== Examples
-
#
-
# class Base
-
# class_attribute :setting
-
# end
-
#
-
# class Subclass < Base
-
# end
-
#
-
# Base.setting = true
-
# Subclass.setting # => true
-
# Subclass.setting = false
-
# Subclass.setting # => false
-
# Base.setting # => true
-
#
-
# In the above case as long as Subclass does not assign a value to setting
-
# by performing <tt>Subclass.setting = _something_</tt>, <tt>Subclass.setting</tt>
-
# would read value assigned to parent class. Once Subclass assigns a value then
-
# the value assigned by Subclass would be returned.
-
#
-
# This matches normal Ruby method inheritance: think of writing an attribute
-
# on a subclass as overriding the reader method. However, you need to be aware
-
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
-
# In such cases, you don't want to do changes in place. Instead use setters:
-
#
-
# Base.setting = []
-
# Base.setting # => []
-
# Subclass.setting # => []
-
#
-
# # Appending in child changes both parent and child because it is the same object:
-
# Subclass.setting << :foo
-
# Base.setting # => [:foo]
-
# Subclass.setting # => [:foo]
-
#
-
# # Use setters to not propagate changes:
-
# Base.setting = []
-
# Subclass.setting += [:foo]
-
# Base.setting # => []
-
# Subclass.setting # => [:foo]
-
#
-
# For convenience, an instance predicate method is defined as well.
-
# To skip it, pass <tt>instance_predicate: false</tt>.
-
#
-
# Subclass.setting? # => false
-
#
-
# Instances may overwrite the class value in the same way:
-
#
-
# Base.setting = true
-
# object = Base.new
-
# object.setting # => true
-
# object.setting = false
-
# object.setting # => false
-
# Base.setting # => true
-
#
-
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# object.setting # => NoMethodError
-
# object.setting? # => NoMethodError
-
#
-
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
-
#
-
# object.setting = false # => NoMethodError
-
#
-
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
-
#
-
# To set a default value for the attribute, pass <tt>default:</tt>, like so:
-
#
-
# class_attribute :settings, default: {}
-
23
def class_attribute(*attrs, instance_accessor: true,
-
instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
-
-
113
class_methods, methods = [], []
-
113
attrs.each do |name|
-
113
unless name.is_a?(Symbol) || name.is_a?(String)
-
raise TypeError, "#{name.inspect} is not a symbol nor a string"
-
end
-
-
113
class_methods << <<~RUBY # In case the method exists and is not public
-
silence_redefinition_of_method def #{name}
-
end
-
RUBY
-
-
113
methods << <<~RUBY if instance_reader
-
silence_redefinition_of_method def #{name}
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
-
end
-
RUBY
-
-
-
113
class_methods << <<~RUBY
-
silence_redefinition_of_method def #{name}=(value)
-
redefine_method(:#{name}) { value } if singleton_class?
-
redefine_singleton_method(:#{name}) { value }
-
value
-
end
-
RUBY
-
-
113
methods << <<~RUBY if instance_writer
-
silence_redefinition_of_method(:#{name}=)
-
attr_writer :#{name}
-
RUBY
-
-
113
if instance_predicate
-
113
class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
-
113
if instance_reader
-
113
methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
-
end
-
end
-
end
-
-
113
location = caller_locations(1, 1).first
-
113
class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
-
-
226
attrs.each { |name| public_send("#{name}=", default) }
-
end
-
end
-
# frozen_string_literal: true
-
-
# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d,
-
# but we keep this around for libraries that directly require it knowing they
-
# want cattr_*. No need to deprecate.
-
require "active_support/core_ext/module/attribute_accessors"
-
# frozen_string_literal: true
-
-
1
class Class
-
# Returns an array with all classes that are < than its receiver.
-
#
-
# class C; end
-
# C.descendants # => []
-
#
-
# class B < C; end
-
# C.descendants # => [B]
-
#
-
# class A < B; end
-
# C.descendants # => [B, A]
-
#
-
# class D < C; end
-
# C.descendants # => [B, A, D]
-
1
def descendants
-
ObjectSpace.each_object(singleton_class).reject do |k|
-
k.singleton_class? || k == self
-
end
-
end
-
-
# Returns an array with the direct children of +self+.
-
#
-
# class Foo; end
-
# class Bar < Foo; end
-
# class Baz < Bar; end
-
#
-
# Foo.subclasses # => [Bar]
-
1
def subclasses
-
descendants.select { |descendant| descendant.superclass == self }
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/date/acts_like"
-
2
require "active_support/core_ext/date/blank"
-
2
require "active_support/core_ext/date/calculations"
-
2
require "active_support/core_ext/date/conversions"
-
2
require "active_support/core_ext/date/zones"
-
# frozen_string_literal: true
-
-
14
require "active_support/core_ext/object/acts_like"
-
-
14
class Date
-
# Duck-types as a Date-like class. See Object#acts_like?.
-
14
def acts_like_date?
-
true
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "date"
-
-
2
class Date #:nodoc:
-
# No Date is blank:
-
#
-
# Date.today.blank? # => false
-
#
-
# @return [false]
-
2
def blank?
-
false
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "date"
-
23
require "active_support/duration"
-
23
require "active_support/core_ext/object/acts_like"
-
23
require "active_support/core_ext/date/zones"
-
23
require "active_support/core_ext/time/zones"
-
23
require "active_support/core_ext/date_and_time/calculations"
-
-
23
class Date
-
23
include DateAndTime::Calculations
-
-
23
class << self
-
23
attr_accessor :beginning_of_week_default
-
-
# Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
-
# If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>.
-
# If no config.beginning_of_week was specified, returns :monday.
-
23
def beginning_of_week
-
Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
-
end
-
-
# Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
-
#
-
# This method accepts any of the following day symbols:
-
# :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
-
23
def beginning_of_week=(week_start)
-
Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
-
end
-
-
# Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol.
-
23
def find_beginning_of_week!(week_start)
-
raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
-
week_start
-
end
-
-
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
-
23
def yesterday
-
::Date.current.yesterday
-
end
-
-
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
-
23
def tomorrow
-
::Date.current.tomorrow
-
end
-
-
# Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
-
23
def current
-
::Time.zone ? ::Time.zone.today : ::Date.today
-
end
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then subtracts the specified number of seconds.
-
23
def ago(seconds)
-
in_time_zone.since(-seconds)
-
end
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
# and then adds the specified number of seconds
-
23
def since(seconds)
-
in_time_zone.since(seconds)
-
end
-
23
alias :in :since
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
-
23
def beginning_of_day
-
in_time_zone
-
end
-
23
alias :midnight :beginning_of_day
-
23
alias :at_midnight :beginning_of_day
-
23
alias :at_beginning_of_day :beginning_of_day
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the middle of the day (12:00)
-
23
def middle_of_day
-
in_time_zone.middle_of_day
-
end
-
23
alias :midday :middle_of_day
-
23
alias :noon :middle_of_day
-
23
alias :at_midday :middle_of_day
-
23
alias :at_noon :middle_of_day
-
23
alias :at_middle_of_day :middle_of_day
-
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
-
23
def end_of_day
-
in_time_zone.end_of_day
-
end
-
23
alias :at_end_of_day :end_of_day
-
-
23
def plus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
plus_without_duration(other)
-
end
-
end
-
23
alias_method :plus_without_duration, :+
-
23
alias_method :+, :plus_with_duration
-
-
23
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
plus_with_duration(-other)
-
else
-
minus_without_duration(other)
-
end
-
end
-
23
alias_method :minus_without_duration, :-
-
23
alias_method :-, :minus_with_duration
-
-
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
-
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
-
23
def advance(options)
-
d = self
-
-
d = d >> options[:years] * 12 if options[:years]
-
d = d >> options[:months] if options[:months]
-
d = d + options[:weeks] * 7 if options[:weeks]
-
d = d + options[:days] if options[:days]
-
-
d
-
end
-
-
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
-
# The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
-
#
-
# Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
-
# Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
-
23
def change(options)
-
::Date.new(
-
options.fetch(:year, year),
-
options.fetch(:month, month),
-
options.fetch(:day, day)
-
)
-
end
-
-
# Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
-
23
def compare_with_coercion(other)
-
if other.is_a?(Time)
-
to_datetime <=> other
-
else
-
compare_without_coercion(other)
-
end
-
end
-
23
alias_method :compare_without_coercion, :<=>
-
23
alias_method :<=>, :compare_with_coercion
-
end
-
# frozen_string_literal: true
-
-
2
require "date"
-
2
require "active_support/inflector/methods"
-
2
require "active_support/core_ext/date/zones"
-
2
require "active_support/core_ext/module/redefine_method"
-
-
2
class Date
-
2
DATE_FORMATS = {
-
short: "%d %b",
-
long: "%B %d, %Y",
-
db: "%Y-%m-%d",
-
inspect: "%Y-%m-%d",
-
number: "%Y%m%d",
-
long_ordinal: lambda { |date|
-
day_format = ActiveSupport::Inflector.ordinalize(date.day)
-
date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
-
},
-
rfc822: "%d %b %Y",
-
iso8601: lambda { |date| date.iso8601 }
-
}
-
-
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
-
#
-
# date.to_formatted_s(:db) # => "2007-11-10"
-
# date.to_s(:db) # => "2007-11-10"
-
#
-
# date.to_formatted_s(:short) # => "10 Nov"
-
# date.to_formatted_s(:number) # => "20071110"
-
# date.to_formatted_s(:long) # => "November 10, 2007"
-
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
-
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
-
# date.to_formatted_s(:iso8601) # => "2007-11-10"
-
#
-
# == Adding your own date formats to to_formatted_s
-
# You can add your own formats to the Date::DATE_FORMATS hash.
-
# Use the format name as the hash key and either a strftime string
-
# or Proc instance that takes a date argument as the value.
-
#
-
# # config/initializers/date_formats.rb
-
# Date::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
-
2
def to_formatted_s(format = :default)
-
if formatter = DATE_FORMATS[format]
-
if formatter.respond_to?(:call)
-
formatter.call(self).to_s
-
else
-
strftime(formatter)
-
end
-
else
-
to_default_s
-
end
-
end
-
2
alias_method :to_default_s, :to_s
-
2
alias_method :to_s, :to_formatted_s
-
-
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
-
2
def readable_inspect
-
strftime("%a, %d %b %Y")
-
end
-
2
alias_method :default_inspect, :inspect
-
2
alias_method :inspect, :readable_inspect
-
-
2
silence_redefinition_of_method :to_time
-
-
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
-
# The timezone can be either :local or :utc (default :local).
-
#
-
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
-
#
-
# date.to_time # => 2007-11-10 00:00:00 0800
-
# date.to_time(:local) # => 2007-11-10 00:00:00 0800
-
#
-
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
-
#
-
# NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ'].
-
# If the *application's* timezone is needed, then use +in_time_zone+ instead.
-
2
def to_time(form = :local)
-
raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
-
::Time.send(form, year, month, day)
-
end
-
-
2
silence_redefinition_of_method :xmlschema
-
-
# Returns a string which represents the time in used time zone as DateTime
-
# defined by XML Schema:
-
#
-
# date = Date.new(2015, 05, 23) # => Sat, 23 May 2015
-
# date.xmlschema # => "2015-05-23T00:00:00+04:00"
-
2
def xmlschema
-
in_time_zone.xmlschema
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "date"
-
23
require "active_support/core_ext/date_and_time/zones"
-
-
23
class Date
-
23
include DateAndTime::Zones
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/object/try"
-
23
require "active_support/core_ext/date_time/conversions"
-
-
23
module DateAndTime
-
23
module Calculations
-
23
DAYS_INTO_WEEK = {
-
sunday: 0,
-
monday: 1,
-
tuesday: 2,
-
wednesday: 3,
-
thursday: 4,
-
friday: 5,
-
saturday: 6
-
}
-
23
WEEKEND_DAYS = [ 6, 0 ]
-
-
# Returns a new date/time representing yesterday.
-
23
def yesterday
-
advance(days: -1)
-
end
-
-
# Returns a new date/time representing tomorrow.
-
23
def tomorrow
-
advance(days: 1)
-
end
-
-
# Returns true if the date/time is today.
-
23
def today?
-
to_date == ::Date.current
-
end
-
-
# Returns true if the date/time is tomorrow.
-
23
def tomorrow?
-
to_date == ::Date.current.tomorrow
-
end
-
23
alias :next_day? :tomorrow?
-
-
# Returns true if the date/time is yesterday.
-
23
def yesterday?
-
to_date == ::Date.current.yesterday
-
end
-
23
alias :prev_day? :yesterday?
-
-
# Returns true if the date/time is in the past.
-
23
def past?
-
self < self.class.current
-
end
-
-
# Returns true if the date/time is in the future.
-
23
def future?
-
self > self.class.current
-
end
-
-
# Returns true if the date/time falls on a Saturday or Sunday.
-
23
def on_weekend?
-
WEEKEND_DAYS.include?(wday)
-
end
-
-
# Returns true if the date/time does not fall on a Saturday or Sunday.
-
23
def on_weekday?
-
!WEEKEND_DAYS.include?(wday)
-
end
-
-
# Returns true if the date/time falls before <tt>date_or_time</tt>.
-
23
def before?(date_or_time)
-
self < date_or_time
-
end
-
-
# Returns true if the date/time falls after <tt>date_or_time</tt>.
-
23
def after?(date_or_time)
-
self > date_or_time
-
end
-
-
# Returns a new date/time the specified number of days ago.
-
23
def days_ago(days)
-
advance(days: -days)
-
end
-
-
# Returns a new date/time the specified number of days in the future.
-
23
def days_since(days)
-
advance(days: days)
-
end
-
-
# Returns a new date/time the specified number of weeks ago.
-
23
def weeks_ago(weeks)
-
advance(weeks: -weeks)
-
end
-
-
# Returns a new date/time the specified number of weeks in the future.
-
23
def weeks_since(weeks)
-
advance(weeks: weeks)
-
end
-
-
# Returns a new date/time the specified number of months ago.
-
23
def months_ago(months)
-
advance(months: -months)
-
end
-
-
# Returns a new date/time the specified number of months in the future.
-
23
def months_since(months)
-
advance(months: months)
-
end
-
-
# Returns a new date/time the specified number of years ago.
-
23
def years_ago(years)
-
advance(years: -years)
-
end
-
-
# Returns a new date/time the specified number of years in the future.
-
23
def years_since(years)
-
advance(years: years)
-
end
-
-
# Returns a new date/time at the start of the month.
-
#
-
# today = Date.today # => Thu, 18 Jun 2015
-
# today.beginning_of_month # => Mon, 01 Jun 2015
-
#
-
# +DateTime+ objects will have a time set to 0:00.
-
#
-
# now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
-
# now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
-
23
def beginning_of_month
-
first_hour(change(day: 1))
-
end
-
23
alias :at_beginning_of_month :beginning_of_month
-
-
# Returns a new date/time at the start of the quarter.
-
#
-
# today = Date.today # => Fri, 10 Jul 2015
-
# today.beginning_of_quarter # => Wed, 01 Jul 2015
-
#
-
# +DateTime+ objects will have a time set to 0:00.
-
#
-
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
-
# now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
-
23
def beginning_of_quarter
-
first_quarter_month = month - (2 + month) % 3
-
beginning_of_month.change(month: first_quarter_month)
-
end
-
23
alias :at_beginning_of_quarter :beginning_of_quarter
-
-
# Returns a new date/time at the end of the quarter.
-
#
-
# today = Date.today # => Fri, 10 Jul 2015
-
# today.end_of_quarter # => Wed, 30 Sep 2015
-
#
-
# +DateTime+ objects will have a time set to 23:59:59.
-
#
-
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
-
# now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
-
23
def end_of_quarter
-
last_quarter_month = month + (12 - month) % 3
-
beginning_of_month.change(month: last_quarter_month).end_of_month
-
end
-
23
alias :at_end_of_quarter :end_of_quarter
-
-
# Returns a new date/time at the beginning of the year.
-
#
-
# today = Date.today # => Fri, 10 Jul 2015
-
# today.beginning_of_year # => Thu, 01 Jan 2015
-
#
-
# +DateTime+ objects will have a time set to 0:00.
-
#
-
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
-
# now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
-
23
def beginning_of_year
-
change(month: 1).beginning_of_month
-
end
-
23
alias :at_beginning_of_year :beginning_of_year
-
-
# Returns a new date/time representing the given day in the next week.
-
#
-
# today = Date.today # => Thu, 07 May 2015
-
# today.next_week # => Mon, 11 May 2015
-
#
-
# The +given_day_in_next_week+ defaults to the beginning of the week
-
# which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
-
# when set.
-
#
-
# today = Date.today # => Thu, 07 May 2015
-
# today.next_week(:friday) # => Fri, 15 May 2015
-
#
-
# +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
-
#
-
# now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
-
# now.next_week # => Mon, 11 May 2015 00:00:00 +0000
-
23
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
-
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
-
same_time ? copy_time_to(result) : result
-
end
-
-
# Returns a new date/time representing the next weekday.
-
23
def next_weekday
-
if next_day.on_weekend?
-
next_week(:monday, same_time: true)
-
else
-
next_day
-
end
-
end
-
-
# Short-hand for months_since(3)
-
23
def next_quarter
-
months_since(3)
-
end
-
-
# Returns a new date/time representing the given day in the previous week.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# DateTime objects have their time set to 0:00 unless +same_time+ is true.
-
23
def prev_week(start_day = Date.beginning_of_week, same_time: false)
-
result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day)))
-
same_time ? copy_time_to(result) : result
-
end
-
23
alias_method :last_week, :prev_week
-
-
# Returns a new date/time representing the previous weekday.
-
23
def prev_weekday
-
if prev_day.on_weekend?
-
copy_time_to(beginning_of_week(:friday))
-
else
-
prev_day
-
end
-
end
-
23
alias_method :last_weekday, :prev_weekday
-
-
# Short-hand for months_ago(1).
-
23
def last_month
-
months_ago(1)
-
end
-
-
# Short-hand for months_ago(3).
-
23
def prev_quarter
-
months_ago(3)
-
end
-
23
alias_method :last_quarter, :prev_quarter
-
-
# Short-hand for years_ago(1).
-
23
def last_year
-
years_ago(1)
-
end
-
-
# Returns the number of days to the start of the week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
23
def days_to_week_start(start_day = Date.beginning_of_week)
-
start_day_number = DAYS_INTO_WEEK.fetch(start_day)
-
(wday - start_day_number) % 7
-
end
-
-
# Returns a new date/time representing the start of this week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# +DateTime+ objects have their time set to 0:00.
-
23
def beginning_of_week(start_day = Date.beginning_of_week)
-
result = days_ago(days_to_week_start(start_day))
-
acts_like?(:time) ? result.midnight : result
-
end
-
23
alias :at_beginning_of_week :beginning_of_week
-
-
# Returns Monday of this week assuming that week starts on Monday.
-
# +DateTime+ objects have their time set to 0:00.
-
23
def monday
-
beginning_of_week(:monday)
-
end
-
-
# Returns a new date/time representing the end of this week on the given day.
-
# Week is assumed to start on +start_day+, default is
-
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
-
# DateTime objects have their time set to 23:59:59.
-
23
def end_of_week(start_day = Date.beginning_of_week)
-
last_hour(days_since(6 - days_to_week_start(start_day)))
-
end
-
23
alias :at_end_of_week :end_of_week
-
-
# Returns Sunday of this week assuming that week starts on Monday.
-
# +DateTime+ objects have their time set to 23:59:59.
-
23
def sunday
-
end_of_week(:monday)
-
end
-
-
# Returns a new date/time representing the end of the month.
-
# DateTime objects will have a time set to 23:59:59.
-
23
def end_of_month
-
last_day = ::Time.days_in_month(month, year)
-
last_hour(days_since(last_day - day))
-
end
-
23
alias :at_end_of_month :end_of_month
-
-
# Returns a new date/time representing the end of the year.
-
# DateTime objects will have a time set to 23:59:59.
-
23
def end_of_year
-
change(month: 12).end_of_month
-
end
-
23
alias :at_end_of_year :end_of_year
-
-
# Returns a Range representing the whole day of the current date/time.
-
23
def all_day
-
beginning_of_day..end_of_day
-
end
-
-
# Returns a Range representing the whole week of the current date/time.
-
# Week starts on start_day, default is <tt>Date.beginning_of_week</tt> or <tt>config.beginning_of_week</tt> when set.
-
23
def all_week(start_day = Date.beginning_of_week)
-
beginning_of_week(start_day)..end_of_week(start_day)
-
end
-
-
# Returns a Range representing the whole month of the current date/time.
-
23
def all_month
-
beginning_of_month..end_of_month
-
end
-
-
# Returns a Range representing the whole quarter of the current date/time.
-
23
def all_quarter
-
beginning_of_quarter..end_of_quarter
-
end
-
-
# Returns a Range representing the whole year of the current date/time.
-
23
def all_year
-
beginning_of_year..end_of_year
-
end
-
-
# Returns a new date/time representing the next occurrence of the specified day of week.
-
#
-
# today = Date.today # => Thu, 14 Dec 2017
-
# today.next_occurring(:monday) # => Mon, 18 Dec 2017
-
# today.next_occurring(:thursday) # => Thu, 21 Dec 2017
-
23
def next_occurring(day_of_week)
-
from_now = DAYS_INTO_WEEK.fetch(day_of_week) - wday
-
from_now += 7 unless from_now > 0
-
advance(days: from_now)
-
end
-
-
# Returns a new date/time representing the previous occurrence of the specified day of week.
-
#
-
# today = Date.today # => Thu, 14 Dec 2017
-
# today.prev_occurring(:monday) # => Mon, 11 Dec 2017
-
# today.prev_occurring(:thursday) # => Thu, 07 Dec 2017
-
23
def prev_occurring(day_of_week)
-
ago = wday - DAYS_INTO_WEEK.fetch(day_of_week)
-
ago += 7 unless ago > 0
-
advance(days: -ago)
-
end
-
-
23
private
-
23
def first_hour(date_or_time)
-
date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
-
end
-
-
23
def last_hour(date_or_time)
-
date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time
-
end
-
-
23
def days_span(day)
-
(DAYS_INTO_WEEK.fetch(day) - DAYS_INTO_WEEK.fetch(Date.beginning_of_week)) % 7
-
end
-
-
23
def copy_time_to(other)
-
other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec))
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/core_ext/module/attribute_accessors"
-
-
24
module DateAndTime
-
24
module Compatibility
-
# If true, +to_time+ preserves the timezone offset of receiver.
-
#
-
# NOTE: With Ruby 2.4+ the default for +to_time+ changed from
-
# converting to the local system time, to preserving the offset
-
# of the receiver. For backwards compatibility we're overriding
-
# this behavior, but new apps will have an initializer that sets
-
# this to true, because the new behavior is preferred.
-
24
mattr_accessor :preserve_timezone, instance_writer: false, default: false
-
-
# Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
-
#
-
# When `true`, it returns local times with an UTC offset, with `false` local
-
# times are returned as UTC.
-
#
-
# # Given this zone:
-
# zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
-
#
-
# # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC:
-
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC
-
#
-
# # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset:
-
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500
-
24
mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module DateAndTime
-
23
module Zones
-
# Returns the simultaneous time in <tt>Time.zone</tt> if a zone is given or
-
# if Time.zone_default is set. Otherwise, it returns the current time.
-
#
-
# Time.zone = 'Hawaii' # => 'Hawaii'
-
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
# Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
-
#
-
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
-
# instead of the operating system's time zone.
-
#
-
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
-
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
-
#
-
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
-
# Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
-
23
def in_time_zone(zone = ::Time.zone)
-
time_zone = ::Time.find_zone! zone
-
time = acts_like?(:time) ? self : nil
-
-
if time_zone
-
time_with_zone(time, time_zone)
-
else
-
time || to_time
-
end
-
end
-
-
23
private
-
23
def time_with_zone(time, zone)
-
if time
-
ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone)
-
else
-
ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc))
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/date_time/acts_like"
-
2
require "active_support/core_ext/date_time/blank"
-
2
require "active_support/core_ext/date_time/calculations"
-
2
require "active_support/core_ext/date_time/compatibility"
-
2
require "active_support/core_ext/date_time/conversions"
-
# frozen_string_literal: true
-
-
2
require "date"
-
2
require "active_support/core_ext/object/acts_like"
-
-
2
class DateTime
-
# Duck-types as a Date-like class. See Object#acts_like?.
-
2
def acts_like_date?
-
true
-
end
-
-
# Duck-types as a Time-like class. See Object#acts_like?.
-
2
def acts_like_time?
-
true
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "date"
-
-
2
class DateTime #:nodoc:
-
# No DateTime is ever blank:
-
#
-
# DateTime.now.blank? # => false
-
#
-
# @return [false]
-
2
def blank?
-
false
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "date"
-
-
23
class DateTime
-
23
class << self
-
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or
-
# <tt>config.time_zone</tt> are set, otherwise returns
-
# <tt>Time.now.to_datetime</tt>.
-
23
def current
-
::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
-
end
-
end
-
-
# Returns the number of seconds since 00:00:00.
-
#
-
# DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
-
# DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
-
# DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
-
23
def seconds_since_midnight
-
sec + (min * 60) + (hour * 3600)
-
end
-
-
# Returns the number of seconds until 23:59:59.
-
#
-
# DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
-
# DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
-
# DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
-
23
def seconds_until_end_of_day
-
end_of_day.to_i - to_i
-
end
-
-
# Returns the fraction of a second as a +Rational+
-
#
-
# DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
-
23
def subsec
-
sec_fraction
-
end
-
-
# Returns a new DateTime where one or more of the elements have been changed
-
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
-
# <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
-
# passed, then minute and sec is set to 0. If the hour and minute is passed,
-
# then sec is set to 0. The +options+ parameter takes a hash with any of these
-
# keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>,
-
# <tt>:min</tt>, <tt>:sec</tt>, <tt>:offset</tt>, <tt>:start</tt>.
-
#
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0)
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
-
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
-
23
def change(options)
-
if new_nsec = options[:nsec]
-
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
-
new_fraction = Rational(new_nsec, 1000000000)
-
else
-
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
-
new_fraction = Rational(new_usec, 1000000)
-
end
-
-
raise ArgumentError, "argument out of range" if new_fraction >= 1
-
-
::DateTime.civil(
-
options.fetch(:year, year),
-
options.fetch(:month, month),
-
options.fetch(:day, day),
-
options.fetch(:hour, hour),
-
options.fetch(:min, options[:hour] ? 0 : min),
-
options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction,
-
options.fetch(:offset, offset),
-
options.fetch(:start, start)
-
)
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days.
-
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
-
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
-
# <tt>:minutes</tt>, <tt>:seconds</tt>.
-
23
def advance(options)
-
unless options[:weeks].nil?
-
options[:weeks], partial_weeks = options[:weeks].divmod(1)
-
options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
-
end
-
-
unless options[:days].nil?
-
options[:days], partial_days = options[:days].divmod(1)
-
options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
-
end
-
-
d = to_date.advance(options)
-
datetime_advanced_by_date = change(year: d.year, month: d.month, day: d.day)
-
seconds_to_advance = \
-
options.fetch(:seconds, 0) +
-
options.fetch(:minutes, 0) * 60 +
-
options.fetch(:hours, 0) * 3600
-
-
if seconds_to_advance.zero?
-
datetime_advanced_by_date
-
else
-
datetime_advanced_by_date.since(seconds_to_advance)
-
end
-
end
-
-
# Returns a new DateTime representing the time a number of seconds ago.
-
# Do not use this method in combination with x.months, use months_ago instead!
-
23
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new DateTime representing the time a number of seconds since the
-
# instance time. Do not use this method in combination with x.months, use
-
# months_since instead!
-
23
def since(seconds)
-
self + Rational(seconds, 86400)
-
end
-
23
alias :in :since
-
-
# Returns a new DateTime representing the start of the day (0:00).
-
23
def beginning_of_day
-
change(hour: 0)
-
end
-
23
alias :midnight :beginning_of_day
-
23
alias :at_midnight :beginning_of_day
-
23
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new DateTime representing the middle of the day (12:00)
-
23
def middle_of_day
-
change(hour: 12)
-
end
-
23
alias :midday :middle_of_day
-
23
alias :noon :middle_of_day
-
23
alias :at_midday :middle_of_day
-
23
alias :at_noon :middle_of_day
-
23
alias :at_middle_of_day :middle_of_day
-
-
# Returns a new DateTime representing the end of the day (23:59:59).
-
23
def end_of_day
-
change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000))
-
end
-
23
alias :at_end_of_day :end_of_day
-
-
# Returns a new DateTime representing the start of the hour (hh:00:00).
-
23
def beginning_of_hour
-
change(min: 0)
-
end
-
23
alias :at_beginning_of_hour :beginning_of_hour
-
-
# Returns a new DateTime representing the end of the hour (hh:59:59).
-
23
def end_of_hour
-
change(min: 59, sec: 59, usec: Rational(999999999, 1000))
-
end
-
23
alias :at_end_of_hour :end_of_hour
-
-
# Returns a new DateTime representing the start of the minute (hh:mm:00).
-
23
def beginning_of_minute
-
change(sec: 0)
-
end
-
23
alias :at_beginning_of_minute :beginning_of_minute
-
-
# Returns a new DateTime representing the end of the minute (hh:mm:59).
-
23
def end_of_minute
-
change(sec: 59, usec: Rational(999999999, 1000))
-
end
-
23
alias :at_end_of_minute :end_of_minute
-
-
# Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
-
23
def localtime(utc_offset = nil)
-
utc = new_offset(0)
-
-
Time.utc(
-
utc.year, utc.month, utc.day,
-
utc.hour, utc.min, utc.sec + utc.sec_fraction
-
).getlocal(utc_offset)
-
end
-
23
alias_method :getlocal, :localtime
-
-
# Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
-
#
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
-
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
-
23
def utc
-
utc = new_offset(0)
-
-
Time.utc(
-
utc.year, utc.month, utc.day,
-
utc.hour, utc.min, utc.sec + utc.sec_fraction
-
)
-
end
-
23
alias_method :getgm, :utc
-
23
alias_method :getutc, :utc
-
23
alias_method :gmtime, :utc
-
-
# Returns +true+ if <tt>offset == 0</tt>.
-
23
def utc?
-
offset == 0
-
end
-
-
# Returns the offset value in seconds.
-
23
def utc_offset
-
(offset * 86400).to_i
-
end
-
-
# Layers additional behavior on DateTime#<=> so that Time and
-
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
-
23
def <=>(other)
-
if other.respond_to? :to_datetime
-
super other.to_datetime rescue nil
-
else
-
super
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/date_and_time/compatibility"
-
2
require "active_support/core_ext/module/redefine_method"
-
-
2
class DateTime
-
2
include DateAndTime::Compatibility
-
-
2
silence_redefinition_of_method :to_time
-
-
# Either return an instance of +Time+ with the same UTC offset
-
# as +self+ or an instance of +Time+ representing the same time
-
# in the local system timezone depending on the setting of
-
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
-
2
def to_time
-
preserve_timezone ? getlocal(utc_offset) : getlocal
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "date"
-
23
require "active_support/inflector/methods"
-
23
require "active_support/core_ext/time/conversions"
-
23
require "active_support/core_ext/date_time/calculations"
-
23
require "active_support/values/time_zone"
-
-
23
class DateTime
-
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# === Examples
-
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
-
#
-
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
-
# datetime.to_s(:number) # => "20071204000000"
-
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
-
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
-
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
-
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
-
# datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00"
-
#
-
# == Adding your own datetime formats to to_formatted_s
-
# DateTime formats are shared with Time. You can add your own to the
-
# Time::DATE_FORMATS hash. Use the format name as the hash key and
-
# either a strftime string or Proc instance that takes a time or
-
# datetime argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
-
23
def to_formatted_s(format = :default)
-
if formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
to_default_s
-
end
-
end
-
23
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
-
23
alias_method :to_s, :to_formatted_s
-
-
# Returns a formatted string of the offset from UTC, or an alternative
-
# string if the time zone is already UTC.
-
#
-
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
-
# datetime.formatted_offset # => "-06:00"
-
# datetime.formatted_offset(false) # => "-0600"
-
23
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
-
23
def readable_inspect
-
to_s(:rfc822)
-
end
-
23
alias_method :default_inspect, :inspect
-
23
alias_method :inspect, :readable_inspect
-
-
# Returns DateTime with local offset for given year if format is local else
-
# offset is zero.
-
#
-
# DateTime.civil_from_format :local, 2012
-
# # => Sun, 01 Jan 2012 00:00:00 +0300
-
# DateTime.civil_from_format :local, 2012, 12, 17
-
# # => Mon, 17 Dec 2012 00:00:00 +0000
-
23
def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0)
-
if utc_or_local.to_sym == :local
-
offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
-
else
-
offset = 0
-
end
-
civil(year, month, day, hour, min, sec, offset)
-
end
-
-
# Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch.
-
23
def to_f
-
seconds_since_unix_epoch.to_f + sec_fraction
-
end
-
-
# Converts +self+ to an integer number of seconds since the Unix epoch.
-
23
def to_i
-
seconds_since_unix_epoch.to_i
-
end
-
-
# Returns the fraction of a second as microseconds
-
23
def usec
-
(sec_fraction * 1_000_000).to_i
-
end
-
-
# Returns the fraction of a second as nanoseconds
-
23
def nsec
-
(sec_fraction * 1_000_000_000).to_i
-
end
-
-
23
private
-
23
def offset_in_seconds
-
(offset * 86400).to_i
-
end
-
-
23
def seconds_since_unix_epoch
-
(jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/digest/uuid"
-
# frozen_string_literal: true
-
-
1
require "securerandom"
-
-
1
module Digest
-
1
module UUID
-
1
DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
-
# Generates a v5 non-random UUID (Universally Unique IDentifier).
-
#
-
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
-
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
-
#
-
# See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
-
1
def self.uuid_from_hash(hash_class, uuid_namespace, name)
-
if hash_class == Digest::MD5
-
version = 3
-
elsif hash_class == Digest::SHA1
-
version = 5
-
else
-
raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
-
end
-
-
hash = hash_class.new
-
hash.update(uuid_namespace)
-
hash.update(name)
-
-
ary = hash.digest.unpack("NnnnnN")
-
ary[2] = (ary[2] & 0x0FFF) | (version << 12)
-
ary[3] = (ary[3] & 0x3FFF) | 0x8000
-
-
"%08x-%04x-%04x-%04x-%04x%08x" % ary
-
end
-
-
# Convenience method for uuid_from_hash using Digest::MD5.
-
1
def self.uuid_v3(uuid_namespace, name)
-
uuid_from_hash(Digest::MD5, uuid_namespace, name)
-
end
-
-
# Convenience method for uuid_from_hash using Digest::SHA1.
-
1
def self.uuid_v5(uuid_namespace, name)
-
uuid_from_hash(Digest::SHA1, uuid_namespace, name)
-
end
-
-
# Convenience method for SecureRandom.uuid.
-
1
def self.uuid_v4
-
SecureRandom.uuid
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module Enumerable
-
23
INDEX_WITH_DEFAULT = Object.new
-
23
private_constant :INDEX_WITH_DEFAULT
-
-
# Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
-
# when we omit an identity.
-
-
# :stopdoc:
-
-
# We can't use Refinements here because Refinements with Module which will be prepended
-
# doesn't work well https://bugs.ruby-lang.org/issues/13446
-
23
alias :_original_sum_with_required_identity :sum
-
23
private :_original_sum_with_required_identity
-
-
# :startdoc:
-
-
# Calculates a sum from the elements.
-
#
-
# payments.sum { |p| p.price * p.tax_rate }
-
# payments.sum(&:price)
-
#
-
# The latter is a shortcut for:
-
#
-
# payments.inject(0) { |sum, p| sum + p.price }
-
#
-
# It can also calculate the sum without the use of a block.
-
#
-
# [5, 15, 10].sum # => 30
-
# ['foo', 'bar'].sum # => "foobar"
-
# [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
-
#
-
# The default sum of an empty list is zero. You can override this default:
-
#
-
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
-
23
def sum(identity = nil, &block)
-
if identity
-
_original_sum_with_required_identity(identity, &block)
-
elsif block_given?
-
map(&block).sum(identity)
-
else
-
inject(:+) || 0
-
end
-
end
-
-
# Convert an enumerable to a hash, using the block result as the key and the
-
# element as the value.
-
#
-
# people.index_by(&:login)
-
# # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
-
#
-
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
-
# # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
-
23
def index_by
-
if block_given?
-
result = {}
-
each { |elem| result[yield(elem)] = elem }
-
result
-
else
-
to_enum(:index_by) { size if respond_to?(:size) }
-
end
-
end
-
-
# Convert an enumerable to a hash, using the element as the key and the block
-
# result as the value.
-
#
-
# post = Post.new(title: "hey there", body: "what's up?")
-
#
-
# %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
-
# # => { title: "hey there", body: "what's up?" }
-
#
-
# If an argument is passed instead of a block, it will be used as the value
-
# for all elements:
-
#
-
# %i( created_at updated_at ).index_with(Time.now)
-
# # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
-
23
def index_with(default = INDEX_WITH_DEFAULT)
-
if block_given?
-
result = {}
-
each { |elem| result[elem] = yield(elem) }
-
result
-
elsif default != INDEX_WITH_DEFAULT
-
result = {}
-
each { |elem| result[elem] = default }
-
result
-
else
-
to_enum(:index_with) { size if respond_to?(:size) }
-
end
-
end
-
-
# Returns +true+ if the enumerable has more than 1 element. Functionally
-
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
-
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
-
# if more than one person is over 26.
-
23
def many?
-
cnt = 0
-
if block_given?
-
any? do |element|
-
cnt += 1 if yield element
-
cnt > 1
-
end
-
else
-
any? { (cnt += 1) > 1 }
-
end
-
end
-
-
# Returns a new array that includes the passed elements.
-
#
-
# [ 1, 2, 3 ].including(4, 5)
-
# # => [ 1, 2, 3, 4, 5 ]
-
#
-
# ["David", "Rafael"].including %w[ Aaron Todd ]
-
# # => ["David", "Rafael", "Aaron", "Todd"]
-
23
def including(*elements)
-
to_a.including(*elements)
-
end
-
-
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
-
# collection does not include the object.
-
23
def exclude?(object)
-
!include?(object)
-
end
-
-
# Returns a copy of the enumerable excluding the specified elements.
-
#
-
# ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
-
# # => ["David", "Rafael"]
-
#
-
# ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
-
# # => ["David", "Rafael"]
-
#
-
# {foo: 1, bar: 2, baz: 3}.excluding :bar
-
# # => {foo: 1, baz: 3}
-
23
def excluding(*elements)
-
elements.flatten!(1)
-
reject { |element| elements.include?(element) }
-
end
-
-
# Alias for #excluding.
-
23
def without(*elements)
-
excluding(*elements)
-
end
-
-
# Extract the given key from each element in the enumerable.
-
#
-
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
-
# # => ["David", "Rafael", "Aaron"]
-
#
-
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
-
# # => [[1, "David"], [2, "Rafael"]]
-
23
def pluck(*keys)
-
if keys.many?
-
map { |element| keys.map { |key| element[key] } }
-
else
-
key = keys.first
-
map { |element| element[key] }
-
end
-
end
-
-
# Extract the given key from the first element in the enumerable.
-
#
-
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
-
# # => "David"
-
#
-
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
-
# # => [1, "David"]
-
23
def pick(*keys)
-
return if none?
-
-
if keys.many?
-
keys.map { |key| first[key] }
-
else
-
first[keys.first]
-
end
-
end
-
-
# Returns a new +Array+ without the blank items.
-
# Uses Object#blank? for determining if an item is blank.
-
#
-
# [1, "", nil, 2, " ", [], {}, false, true].compact_blank
-
# # => [1, 2, true]
-
#
-
# Set.new([nil, "", 1, 2])
-
# # => [2, 1] (or [1, 2])
-
#
-
# When called on a +Hash+, returns a new +Hash+ without the blank values.
-
#
-
# { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
-
# #=> { b: 1, f: true }
-
23
def compact_blank
-
reject(&:blank?)
-
end
-
end
-
-
23
class Hash
-
# Hash#reject has its own definition, so this needs one too.
-
23
def compact_blank #:nodoc:
-
reject { |_k, v| v.blank? }
-
end
-
-
# Removes all blank values from the +Hash+ in place and returns self.
-
# Uses Object#blank? for determining if a value is blank.
-
#
-
# h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
-
# h.compact_blank!
-
# # => { b: 1, f: true }
-
23
def compact_blank!
-
# use delete_if rather than reject! because it always returns self even if nothing changed
-
delete_if { |_k, v| v.blank? }
-
end
-
end
-
-
23
class Range #:nodoc:
-
# Optimize range sum to use arithmetic progression if a block is not given and
-
# we have a range of numeric values.
-
23
def sum(identity = nil)
-
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
-
super
-
else
-
actual_last = exclude_end? ? (last - 1) : last
-
if actual_last >= first
-
sum = identity || 0
-
sum + (actual_last - first + 1) * (actual_last + first) / 2
-
else
-
identity || 0
-
end
-
end
-
end
-
end
-
-
# Using Refinements here in order not to expose our internal method
-
23
using Module.new {
-
23
refine Array do
-
23
alias :orig_sum :sum
-
end
-
}
-
-
23
class Array #:nodoc:
-
# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
-
23
def sum(init = nil, &block)
-
if init.is_a?(Numeric) || first.is_a?(Numeric)
-
init ||= 0
-
orig_sum(init, &block)
-
else
-
super
-
end
-
end
-
-
# Removes all blank elements from the +Array+ in place and returns self.
-
# Uses Object#blank? for determining if an item is blank.
-
#
-
# a = [1, "", nil, 2, " ", [], {}, false, true]
-
# a.compact_blank!
-
# # => [1, 2, true]
-
23
def compact_blank!
-
# use delete_if rather than reject! because it always returns self even if nothing changed
-
delete_if(&:blank?)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/file/atomic"
-
# frozen_string_literal: true
-
-
1
require "fileutils"
-
-
1
class File
-
# Write to a file atomically. Useful for situations where you don't
-
# want other processes or threads to see half-written files.
-
#
-
# File.atomic_write('important.file') do |file|
-
# file.write('hello')
-
# end
-
#
-
# This method needs to create a temporary file. By default it will create it
-
# in the same directory as the destination file. If you don't like this
-
# behavior you can provide a different directory but it must be on the
-
# same physical filesystem as the file you're trying to write.
-
#
-
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
-
# file.write('hello')
-
# end
-
1
def self.atomic_write(file_name, temp_dir = dirname(file_name))
-
require "tempfile" unless defined?(Tempfile)
-
-
Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
-
temp_file.binmode
-
return_val = yield temp_file
-
temp_file.close
-
-
old_stat = if exist?(file_name)
-
# Get original file permissions
-
stat(file_name)
-
else
-
# If not possible, probe which are the default permissions in the
-
# destination directory.
-
probe_stat_in(dirname(file_name))
-
end
-
-
if old_stat
-
# Set correct permissions on new file
-
begin
-
chown(old_stat.uid, old_stat.gid, temp_file.path)
-
# This operation will affect filesystem ACL's
-
chmod(old_stat.mode, temp_file.path)
-
rescue Errno::EPERM, Errno::EACCES
-
# Changing file ownership failed, moving on.
-
end
-
end
-
-
# Overwrite original file with temp file
-
rename(temp_file.path, file_name)
-
return_val
-
end
-
end
-
-
# Private utility method.
-
1
def self.probe_stat_in(dir) #:nodoc:
-
basename = [
-
".permissions_check",
-
Thread.current.object_id,
-
Process.pid,
-
rand(1000000)
-
].join(".")
-
-
file_name = join(dir, basename)
-
FileUtils.touch(file_name)
-
stat(file_name)
-
ensure
-
FileUtils.rm_f(file_name) if file_name
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/hash/conversions"
-
1
require "active_support/core_ext/hash/deep_merge"
-
1
require "active_support/core_ext/hash/deep_transform_values"
-
1
require "active_support/core_ext/hash/except"
-
1
require "active_support/core_ext/hash/indifferent_access"
-
1
require "active_support/core_ext/hash/keys"
-
1
require "active_support/core_ext/hash/reverse_merge"
-
1
require "active_support/core_ext/hash/slice"
-
# frozen_string_literal: true
-
-
require "active_support/deprecation"
-
-
ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
-
# frozen_string_literal: true
-
-
1
require "active_support/xml_mini"
-
1
require "active_support/core_ext/object/blank"
-
1
require "active_support/core_ext/object/to_param"
-
1
require "active_support/core_ext/object/to_query"
-
1
require "active_support/core_ext/object/try"
-
1
require "active_support/core_ext/array/wrap"
-
1
require "active_support/core_ext/hash/reverse_merge"
-
1
require "active_support/core_ext/string/inflections"
-
-
1
class Hash
-
# Returns a string containing an XML representation of its receiver:
-
#
-
# { foo: 1, bar: 2 }.to_xml
-
# # =>
-
# # <?xml version="1.0" encoding="UTF-8"?>
-
# # <hash>
-
# # <foo type="integer">1</foo>
-
# # <bar type="integer">2</bar>
-
# # </hash>
-
#
-
# To do so, the method loops over the pairs and builds nodes that depend on
-
# the _values_. Given a pair +key+, +value+:
-
#
-
# * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>.
-
#
-
# * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>,
-
# and +key+ singularized as <tt>:children</tt>.
-
#
-
# * If +value+ is a callable object it must expect one or two arguments. Depending
-
# on the arity, the callable is invoked with the +options+ hash as first argument
-
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
-
# callable can add nodes by using <tt>options[:builder]</tt>.
-
#
-
# {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
-
# # => "<b>foo</b>"
-
#
-
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
-
#
-
# class Foo
-
# def to_xml(options)
-
# options[:builder].bar 'fooing!'
-
# end
-
# end
-
#
-
# { foo: Foo.new }.to_xml(skip_instruct: true)
-
# # =>
-
# # <hash>
-
# # <bar>fooing!</bar>
-
# # </hash>
-
#
-
# * Otherwise, a node with +key+ as tag is created with a string representation of
-
# +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
-
# Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is
-
# added as well according to the following mapping:
-
#
-
# XML_TYPE_NAMES = {
-
# "Symbol" => "symbol",
-
# "Integer" => "integer",
-
# "BigDecimal" => "decimal",
-
# "Float" => "float",
-
# "TrueClass" => "boolean",
-
# "FalseClass" => "boolean",
-
# "Date" => "date",
-
# "DateTime" => "dateTime",
-
# "Time" => "dateTime"
-
# }
-
#
-
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
-
#
-
# The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can
-
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
-
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
-
1
def to_xml(options = {})
-
require "active_support/builder" unless defined?(Builder::XmlMarkup)
-
-
options = options.dup
-
options[:indent] ||= 2
-
options[:root] ||= "hash"
-
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
-
-
builder = options[:builder]
-
builder.instruct! unless options.delete(:skip_instruct)
-
-
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
-
-
builder.tag!(root) do
-
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
-
yield builder if block_given?
-
end
-
end
-
-
1
class << self
-
# Returns a Hash containing a collection of pairs when the key is the node name and the value is
-
# its content
-
#
-
# xml = <<-XML
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <hash>
-
# <foo type="integer">1</foo>
-
# <bar type="integer">2</bar>
-
# </hash>
-
# XML
-
#
-
# hash = Hash.from_xml(xml)
-
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
-
#
-
# +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
-
# <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
-
# parse this XML.
-
#
-
# Custom +disallowed_types+ can also be passed in the form of an
-
# array.
-
#
-
# xml = <<-XML
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <hash>
-
# <foo type="integer">1</foo>
-
# <bar type="string">"David"</bar>
-
# </hash>
-
# XML
-
#
-
# hash = Hash.from_xml(xml, ['integer'])
-
# # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
-
#
-
# Note that passing custom disallowed types will override the default types,
-
# which are Symbol and YAML.
-
1
def from_xml(xml, disallowed_types = nil)
-
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
-
end
-
-
# Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
-
1
def from_trusted_xml(xml)
-
from_xml xml, []
-
end
-
end
-
end
-
-
1
module ActiveSupport
-
1
class XMLConverter # :nodoc:
-
# Raised if the XML contains attributes with type="yaml" or
-
# type="symbol". Read Hash#from_xml for more details.
-
1
class DisallowedType < StandardError
-
1
def initialize(type)
-
super "Disallowed type attribute: #{type.inspect}"
-
end
-
end
-
-
1
DISALLOWED_TYPES = %w(symbol yaml)
-
-
1
def initialize(xml, disallowed_types = nil)
-
@xml = normalize_keys(XmlMini.parse(xml))
-
@disallowed_types = disallowed_types || DISALLOWED_TYPES
-
end
-
-
1
def to_h
-
deep_to_h(@xml)
-
end
-
-
1
private
-
1
def normalize_keys(params)
-
case params
-
when Hash
-
Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
-
when Array
-
params.map { |v| normalize_keys(v) }
-
else
-
params
-
end
-
end
-
-
1
def deep_to_h(value)
-
case value
-
when Hash
-
process_hash(value)
-
when Array
-
process_array(value)
-
when String
-
value
-
else
-
raise "can't typecast #{value.class.name} - #{value.inspect}"
-
end
-
end
-
-
1
def process_hash(value)
-
if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"])
-
raise DisallowedType, value["type"]
-
end
-
-
if become_array?(value)
-
_, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) })
-
if entries.nil? || value["__content__"].try(:empty?)
-
[]
-
else
-
case entries
-
when Array
-
entries.collect { |v| deep_to_h(v) }
-
when Hash
-
[deep_to_h(entries)]
-
else
-
raise "can't typecast #{entries.inspect}"
-
end
-
end
-
elsif become_content?(value)
-
process_content(value)
-
-
elsif become_empty_string?(value)
-
""
-
elsif become_hash?(value)
-
xml_value = value.transform_values { |v| deep_to_h(v) }
-
-
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
-
# how multipart uploaded files from HTML appear
-
xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
-
end
-
end
-
-
1
def become_content?(value)
-
value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
-
end
-
-
1
def become_array?(value)
-
value["type"] == "array"
-
end
-
-
1
def become_empty_string?(value)
-
# { "string" => true }
-
# No tests fail when the second term is removed.
-
value["type"] == "string" && value["nil"] != "true"
-
end
-
-
1
def become_hash?(value)
-
!nothing?(value) && !garbage?(value)
-
end
-
-
1
def nothing?(value)
-
# blank or nil parsed values are represented by nil
-
value.blank? || value["nil"] == "true"
-
end
-
-
1
def garbage?(value)
-
# If the type is the only element which makes it then
-
# this still makes the value nil, except if type is
-
# an XML node(where type['value'] is a Hash)
-
value["type"] && !value["type"].is_a?(::Hash) && value.size == 1
-
end
-
-
1
def process_content(value)
-
content = value["__content__"]
-
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
-
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
-
else
-
content
-
end
-
end
-
-
1
def process_array(value)
-
value.map! { |i| deep_to_h(i) }
-
value.length > 1 ? value : value.first
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
class Hash
-
# Returns a new hash with +self+ and +other_hash+ merged recursively.
-
#
-
# h1 = { a: true, b: { c: [1, 2, 3] } }
-
# h2 = { a: false, b: { x: [3, 4, 5] } }
-
#
-
# h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
-
#
-
# Like with Hash#merge in the standard library, a block can be provided
-
# to merge values:
-
#
-
# h1 = { a: 100, b: 200, c: { c1: 100 } }
-
# h2 = { b: 250, c: { c1: 200 } }
-
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
-
# # => { a: 100, b: 450, c: { c1: 300 } }
-
24
def deep_merge(other_hash, &block)
-
dup.deep_merge!(other_hash, &block)
-
end
-
-
# Same as +deep_merge+, but modifies +self+.
-
24
def deep_merge!(other_hash, &block)
-
merge!(other_hash) do |key, this_val, other_val|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
-
this_val.deep_merge(other_val, &block)
-
elsif block_given?
-
block.call(key, this_val, other_val)
-
else
-
other_val
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Hash
-
# Returns a new hash with all values converted by the block operation.
-
# This includes the values from the root hash and from all
-
# nested hashes and arrays.
-
#
-
# hash = { person: { name: 'Rob', age: '28' } }
-
#
-
# hash.deep_transform_values{ |value| value.to_s.upcase }
-
# # => {person: {name: "ROB", age: "28"}}
-
1
def deep_transform_values(&block)
-
_deep_transform_values_in_object(self, &block)
-
end
-
-
# Destructively converts all values by using the block operation.
-
# This includes the values from the root hash and from all
-
# nested hashes and arrays.
-
1
def deep_transform_values!(&block)
-
_deep_transform_values_in_object!(self, &block)
-
end
-
-
1
private
-
# Support methods for deep transforming nested hashes and arrays.
-
1
def _deep_transform_values_in_object(object, &block)
-
case object
-
when Hash
-
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
-
when Array
-
object.map { |e| _deep_transform_values_in_object(e, &block) }
-
else
-
yield(object)
-
end
-
end
-
-
1
def _deep_transform_values_in_object!(object, &block)
-
case object
-
when Hash
-
object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
-
when Array
-
object.map! { |e| _deep_transform_values_in_object!(e, &block) }
-
else
-
yield(object)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
class Hash
-
# Returns a hash that includes everything except given keys.
-
# hash = { a: true, b: false, c: nil }
-
# hash.except(:c) # => { a: true, b: false }
-
# hash.except(:a, :b) # => { c: nil }
-
# hash # => { a: true, b: false, c: nil }
-
#
-
# This is useful for limiting a set of parameters to everything but a few known toggles:
-
# @person.update(params[:person].except(:admin))
-
def except(*keys)
-
slice(*self.keys - keys)
-
24
end unless method_defined?(:except)
-
-
# Removes the given keys from hash and returns it.
-
# hash = { a: true, b: false, c: nil }
-
# hash.except!(:c) # => { a: true, b: false }
-
# hash # => { a: true, b: false }
-
24
def except!(*keys)
-
keys.each { |key| delete(key) }
-
self
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/hash_with_indifferent_access"
-
-
1
class Hash
-
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
-
#
-
# { a: 1 }.with_indifferent_access['a'] # => 1
-
1
def with_indifferent_access
-
ActiveSupport::HashWithIndifferentAccess.new(self)
-
end
-
-
# Called when object is nested under an object that receives
-
# #with_indifferent_access. This method will be called on the current object
-
# by the enclosing object and is aliased to #with_indifferent_access by
-
# default. Subclasses of Hash may overwrite this method to return +self+ if
-
# converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
-
# desirable.
-
#
-
# b = { b: 1 }
-
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
-
# # => {"b"=>1}
-
1
alias nested_under_indifferent_access with_indifferent_access
-
end
-
# frozen_string_literal: true
-
-
23
class Hash
-
# Returns a new hash with all keys converted to strings.
-
#
-
# hash = { name: 'Rob', age: '28' }
-
#
-
# hash.stringify_keys
-
# # => {"name"=>"Rob", "age"=>"28"}
-
23
def stringify_keys
-
transform_keys(&:to_s)
-
end
-
-
# Destructively converts all keys to strings. Same as
-
# +stringify_keys+, but modifies +self+.
-
23
def stringify_keys!
-
transform_keys!(&:to_s)
-
end
-
-
# Returns a new hash with all keys converted to symbols, as long as
-
# they respond to +to_sym+.
-
#
-
# hash = { 'name' => 'Rob', 'age' => '28' }
-
#
-
# hash.symbolize_keys
-
# # => {:name=>"Rob", :age=>"28"}
-
23
def symbolize_keys
-
transform_keys { |key| key.to_sym rescue key }
-
end
-
23
alias_method :to_options, :symbolize_keys
-
-
# Destructively converts all keys to symbols, as long as they respond
-
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
-
23
def symbolize_keys!
-
transform_keys! { |key| key.to_sym rescue key }
-
end
-
23
alias_method :to_options!, :symbolize_keys!
-
-
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
-
# +ArgumentError+ on a mismatch.
-
#
-
# Note that keys are treated differently than HashWithIndifferentAccess,
-
# meaning that string and symbol keys will not match.
-
#
-
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
-
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
-
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
-
23
def assert_valid_keys(*valid_keys)
-
valid_keys.flatten!
-
each_key do |k|
-
unless valid_keys.include?(k)
-
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
-
end
-
end
-
end
-
-
# Returns a new hash with all keys converted by the block operation.
-
# This includes the keys from the root hash and from all
-
# nested hashes and arrays.
-
#
-
# hash = { person: { name: 'Rob', age: '28' } }
-
#
-
# hash.deep_transform_keys{ |key| key.to_s.upcase }
-
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
-
23
def deep_transform_keys(&block)
-
_deep_transform_keys_in_object(self, &block)
-
end
-
-
# Destructively converts all keys by using the block operation.
-
# This includes the keys from the root hash and from all
-
# nested hashes and arrays.
-
23
def deep_transform_keys!(&block)
-
_deep_transform_keys_in_object!(self, &block)
-
end
-
-
# Returns a new hash with all keys converted to strings.
-
# This includes the keys from the root hash and from all
-
# nested hashes and arrays.
-
#
-
# hash = { person: { name: 'Rob', age: '28' } }
-
#
-
# hash.deep_stringify_keys
-
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
-
23
def deep_stringify_keys
-
deep_transform_keys(&:to_s)
-
end
-
-
# Destructively converts all keys to strings.
-
# This includes the keys from the root hash and from all
-
# nested hashes and arrays.
-
23
def deep_stringify_keys!
-
deep_transform_keys!(&:to_s)
-
end
-
-
# Returns a new hash with all keys converted to symbols, as long as
-
# they respond to +to_sym+. This includes the keys from the root hash
-
# and from all nested hashes and arrays.
-
#
-
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
-
#
-
# hash.deep_symbolize_keys
-
# # => {:person=>{:name=>"Rob", :age=>"28"}}
-
23
def deep_symbolize_keys
-
deep_transform_keys { |key| key.to_sym rescue key }
-
end
-
-
# Destructively converts all keys to symbols, as long as they respond
-
# to +to_sym+. This includes the keys from the root hash and from all
-
# nested hashes and arrays.
-
23
def deep_symbolize_keys!
-
deep_transform_keys! { |key| key.to_sym rescue key }
-
end
-
-
23
private
-
# Support methods for deep transforming nested hashes and arrays.
-
23
def _deep_transform_keys_in_object(object, &block)
-
case object
-
when Hash
-
object.each_with_object({}) do |(key, value), result|
-
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
-
end
-
when Array
-
object.map { |e| _deep_transform_keys_in_object(e, &block) }
-
else
-
object
-
end
-
end
-
-
23
def _deep_transform_keys_in_object!(object, &block)
-
case object
-
when Hash
-
object.keys.each do |key|
-
value = object.delete(key)
-
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
-
end
-
object
-
when Array
-
object.map! { |e| _deep_transform_keys_in_object!(e, &block) }
-
else
-
object
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Hash
-
# Merges the caller into +other_hash+. For example,
-
#
-
# options = options.reverse_merge(size: 25, velocity: 10)
-
#
-
# is equivalent to
-
#
-
# options = { size: 25, velocity: 10 }.merge(options)
-
#
-
# This is particularly useful for initializing an options hash
-
# with default values.
-
1
def reverse_merge(other_hash)
-
other_hash.merge(self)
-
end
-
1
alias_method :with_defaults, :reverse_merge
-
-
# Destructive +reverse_merge+.
-
1
def reverse_merge!(other_hash)
-
replace(reverse_merge(other_hash))
-
end
-
1
alias_method :reverse_update, :reverse_merge!
-
1
alias_method :with_defaults!, :reverse_merge!
-
end
-
# frozen_string_literal: true
-
-
24
class Hash
-
# Replaces the hash with only the given keys.
-
# Returns a hash containing the removed key/value pairs.
-
#
-
# hash = { a: 1, b: 2, c: 3, d: 4 }
-
# hash.slice!(:a, :b) # => {:c=>3, :d=>4}
-
# hash # => {:a=>1, :b=>2}
-
24
def slice!(*keys)
-
omit = slice(*self.keys - keys)
-
hash = slice(*keys)
-
hash.default = default
-
hash.default_proc = default_proc if default_proc
-
replace(hash)
-
omit
-
end
-
-
# Removes and returns the key/value pairs matching the given keys.
-
#
-
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
-
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
-
24
def extract!(*keys)
-
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/deprecation"
-
-
ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/integer/multiple"
-
1
require "active_support/core_ext/integer/inflections"
-
1
require "active_support/core_ext/integer/time"
-
# frozen_string_literal: true
-
-
1
require "active_support/inflector"
-
-
1
class Integer
-
# Ordinalize turns a number into an ordinal string used to denote the
-
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# 1.ordinalize # => "1st"
-
# 2.ordinalize # => "2nd"
-
# 1002.ordinalize # => "1002nd"
-
# 1003.ordinalize # => "1003rd"
-
# -11.ordinalize # => "-11th"
-
# -1001.ordinalize # => "-1001st"
-
1
def ordinalize
-
ActiveSupport::Inflector.ordinalize(self)
-
end
-
-
# Ordinal returns the suffix used to denote the position
-
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# 1.ordinal # => "st"
-
# 2.ordinal # => "nd"
-
# 1002.ordinal # => "nd"
-
# 1003.ordinal # => "rd"
-
# -11.ordinal # => "th"
-
# -1001.ordinal # => "st"
-
1
def ordinal
-
ActiveSupport::Inflector.ordinal(self)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Integer
-
# Check whether the integer is evenly divisible by the argument.
-
#
-
# 0.multiple_of?(0) # => true
-
# 6.multiple_of?(5) # => false
-
# 10.multiple_of?(2) # => true
-
1
def multiple_of?(number)
-
number == 0 ? self == 0 : self % number == 0
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/duration"
-
2
require "active_support/core_ext/numeric/time"
-
-
2
class Integer
-
# Returns a Duration instance matching the number of months provided.
-
#
-
# 2.months # => 2 months
-
2
def months
-
ActiveSupport::Duration.months(self)
-
end
-
2
alias :month :months
-
-
# Returns a Duration instance matching the number of years provided.
-
#
-
# 2.years # => 2 years
-
2
def years
-
ActiveSupport::Duration.years(self)
-
end
-
2
alias :year :years
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/kernel/concern"
-
1
require "active_support/core_ext/kernel/reporting"
-
1
require "active_support/core_ext/kernel/singleton_class"
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/module/concerning"
-
-
1
module Kernel
-
1
module_function
-
-
# A shortcut to define a toplevel concern, not within a module.
-
#
-
# See Module::Concerning for more.
-
1
def concern(topic, &module_definition)
-
Object.concern topic, &module_definition
-
end
-
end
-
# frozen_string_literal: true
-
-
24
module Kernel
-
24
module_function
-
-
# Sets $VERBOSE to +nil+ for the duration of the block and back to its original
-
# value afterwards.
-
#
-
# silence_warnings do
-
# value = noisy_call # no warning voiced
-
# end
-
#
-
# noisy_call # warning voiced
-
24
def silence_warnings
-
48
with_warnings(nil) { yield }
-
end
-
-
# Sets $VERBOSE to +true+ for the duration of the block and back to its
-
# original value afterwards.
-
24
def enable_warnings
-
with_warnings(true) { yield }
-
end
-
-
# Sets $VERBOSE for the duration of the block and back to its original
-
# value afterwards.
-
24
def with_warnings(flag)
-
24
old_verbose, $VERBOSE = $VERBOSE, flag
-
24
yield
-
ensure
-
24
$VERBOSE = old_verbose
-
end
-
-
# Blocks and ignores any exception passed as argument if raised within the block.
-
#
-
# suppress(ZeroDivisionError) do
-
# 1/0
-
# puts 'This code is NOT reached'
-
# end
-
#
-
# puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
-
24
def suppress(*exception_classes)
-
yield
-
rescue *exception_classes
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Kernel
-
# class_eval on an object acts like singleton_class.class_eval.
-
1
def class_eval(*args, &block)
-
singleton_class.class_eval(*args, &block)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
class LoadError
-
# Returns true if the given path name (except perhaps for the ".rb"
-
# extension) is the missing file which caused the exception to be raised.
-
3
def is_missing?(location)
-
location.delete_suffix(".rb") == path.to_s.delete_suffix(".rb")
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/inflections"
-
-
3
module ActiveSupport
-
3
module MarshalWithAutoloading # :nodoc:
-
3
def load(source, proc = nil)
-
55997
super(source, proc)
-
rescue ArgumentError, NameError => exc
-
if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
-
# try loading the class/module
-
loaded = $1.constantize
-
-
raise unless $1 == loaded.name
-
-
# if it is an IO we need to go back to read the object
-
source.rewind if source.respond_to?(:rewind)
-
retry
-
else
-
raise exc
-
end
-
end
-
end
-
end
-
-
3
Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading)
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/module/aliasing"
-
1
require "active_support/core_ext/module/introspection"
-
1
require "active_support/core_ext/module/anonymous"
-
1
require "active_support/core_ext/module/attribute_accessors"
-
1
require "active_support/core_ext/module/attribute_accessors_per_thread"
-
1
require "active_support/core_ext/module/attr_internal"
-
1
require "active_support/core_ext/module/concerning"
-
1
require "active_support/core_ext/module/delegation"
-
1
require "active_support/core_ext/module/deprecation"
-
1
require "active_support/core_ext/module/redefine_method"
-
1
require "active_support/core_ext/module/remove_method"
-
# frozen_string_literal: true
-
-
3
class Module
-
# Allows you to make aliases for attributes, which includes
-
# getter, setter, and a predicate.
-
#
-
# class Content < ActiveRecord::Base
-
# # has a title attribute
-
# end
-
#
-
# class Email < Content
-
# alias_attribute :subject, :title
-
# end
-
#
-
# e = Email.find(1)
-
# e.title # => "Superstars"
-
# e.subject # => "Superstars"
-
# e.subject? # => true
-
# e.subject = "Megastars"
-
# e.title # => "Megastars"
-
3
def alias_attribute(new_name, old_name)
-
# The following reader methods use an explicit `self` receiver in order to
-
# support aliases that start with an uppercase letter. Otherwise, they would
-
# be resolved as constants instead.
-
2
module_eval <<-STR, __FILE__, __LINE__ + 1
-
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
-
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
-
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
-
STR
-
end
-
end
-
# frozen_string_literal: true
-
-
3
class Module
-
# A module may or may not have a name.
-
#
-
# module M; end
-
# M.name # => "M"
-
#
-
# m = Module.new
-
# m.name # => nil
-
#
-
# +anonymous?+ method returns true if module does not have a name, false otherwise:
-
#
-
# Module.new.anonymous? # => true
-
#
-
# module M; end
-
# M.anonymous? # => false
-
#
-
# A module gets a name when it is first assigned to a constant. Either
-
# via the +module+ or +class+ keyword or by an explicit assignment:
-
#
-
# m = Module.new # creates an anonymous module
-
# m.anonymous? # => true
-
# M = m # m gets a name here as a side-effect
-
# m.name # => "M"
-
# m.anonymous? # => false
-
3
def anonymous?
-
name.nil?
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Module
-
# Declares an attribute reader backed by an internally-named instance variable.
-
1
def attr_internal_reader(*attrs)
-
attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
-
end
-
-
# Declares an attribute writer backed by an internally-named instance variable.
-
1
def attr_internal_writer(*attrs)
-
attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
-
end
-
-
# Declares an attribute reader and writer backed by an internally-named instance
-
# variable.
-
1
def attr_internal_accessor(*attrs)
-
attr_internal_reader(*attrs)
-
attr_internal_writer(*attrs)
-
end
-
1
alias_method :attr_internal, :attr_internal_accessor
-
-
2
class << self; attr_accessor :attr_internal_naming_format end
-
1
self.attr_internal_naming_format = "@_%s"
-
-
1
private
-
1
def attr_internal_ivar_name(attr)
-
Module.attr_internal_naming_format % attr
-
end
-
-
1
def attr_internal_define(attr_name, type)
-
internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@")
-
# use native attr_* methods as they are faster on some Ruby implementations
-
send("attr_#{type}", internal_name)
-
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
-
alias_method attr_name, internal_name
-
remove_method internal_name
-
end
-
end
-
# frozen_string_literal: true
-
-
# Extends the module object with class/module and instance accessors for
-
# class/module attributes, just like the native attr* accessors for instance
-
# attributes.
-
24
class Module
-
# Defines a class attribute and creates a class and instance reader methods.
-
# The underlying class variable is set to +nil+, if it is not previously
-
# defined. All class and instance methods created will be public, even if
-
# this method is called with a private or protected access modifier.
-
#
-
# module HairColors
-
# mattr_reader :hair_colors
-
# end
-
#
-
# HairColors.hair_colors # => nil
-
# HairColors.class_variable_set("@@hair_colors", [:brown, :black])
-
# HairColors.hair_colors # => [:brown, :black]
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# module Foo
-
# mattr_reader :"1_Badname"
-
# end
-
# # => NameError: invalid attribute name: 1_Badname
-
#
-
# To omit the instance reader method, pass
-
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
-
#
-
# module HairColors
-
# mattr_reader :hair_colors, instance_reader: false
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.new.hair_colors # => NoMethodError
-
#
-
# You can set a default value for the attribute.
-
#
-
# module HairColors
-
# mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-
24
def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
-
280
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
-
280
location ||= caller_locations(1, 1).first
-
-
280
definition = []
-
280
syms.each do |sym|
-
280
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
-
-
280
definition << "def self.#{sym}; @@#{sym}; end"
-
-
280
if instance_reader && instance_accessor
-
252
definition << "def #{sym}; @@#{sym}; end"
-
end
-
-
280
sym_default_value = (block_given? && default.nil?) ? yield : default
-
280
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
-
end
-
-
280
module_eval(definition.join(";"), location.path, location.lineno)
-
end
-
24
alias :cattr_reader :mattr_reader
-
-
# Defines a class attribute and creates a class and instance writer methods to
-
# allow assignment to the attribute. All class and instance methods created
-
# will be public, even if this method is called with a private or protected
-
# access modifier.
-
#
-
# module HairColors
-
# mattr_writer :hair_colors
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# HairColors.hair_colors = [:brown, :black]
-
# Person.class_variable_get("@@hair_colors") # => [:brown, :black]
-
# Person.new.hair_colors = [:blonde, :red]
-
# HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
-
#
-
# To omit the instance writer method, pass
-
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
-
#
-
# module HairColors
-
# mattr_writer :hair_colors, instance_writer: false
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
-
#
-
# You can set a default value for the attribute.
-
#
-
# module HairColors
-
# mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
-
24
def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
-
186
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
-
186
location ||= caller_locations(1, 1).first
-
-
186
definition = []
-
186
syms.each do |sym|
-
186
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
-
186
definition << "def self.#{sym}=(val); @@#{sym} = val; end"
-
-
186
if instance_writer && instance_accessor
-
110
definition << "def #{sym}=(val); @@#{sym} = val; end"
-
end
-
-
186
sym_default_value = (block_given? && default.nil?) ? yield : default
-
186
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
-
end
-
-
186
module_eval(definition.join(";"), location.path, location.lineno)
-
end
-
24
alias :cattr_writer :mattr_writer
-
-
# Defines both class and instance accessors for class attributes.
-
# All class and instance methods created will be public, even if
-
# this method is called with a private or protected access modifier.
-
#
-
# module HairColors
-
# mattr_accessor :hair_colors
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# HairColors.hair_colors = [:brown, :black, :blonde, :red]
-
# HairColors.hair_colors # => [:brown, :black, :blonde, :red]
-
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-
#
-
# If a subclass changes the value then that would also change the value for
-
# parent class. Similarly if parent class changes the value then that would
-
# change the value of subclasses too.
-
#
-
# class Citizen < Person
-
# end
-
#
-
# Citizen.new.hair_colors << :blue
-
# Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
-
#
-
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# module HairColors
-
# mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.new.hair_colors = [:brown] # => NoMethodError
-
# Person.new.hair_colors # => NoMethodError
-
#
-
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
-
#
-
# module HairColors
-
# mattr_accessor :hair_colors, instance_accessor: false
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.new.hair_colors = [:brown] # => NoMethodError
-
# Person.new.hair_colors # => NoMethodError
-
#
-
# You can set a default value for the attribute.
-
#
-
# module HairColors
-
# mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
-
# end
-
#
-
# class Person
-
# include HairColors
-
# end
-
#
-
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
-
24
def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
-
186
location = caller_locations(1, 1).first
-
186
mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
-
186
mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location)
-
end
-
24
alias :cattr_accessor :mattr_accessor
-
end
-
# frozen_string_literal: true
-
-
# Extends the module object with class/module and instance accessors for
-
# class/module attributes, just like the native attr* accessors for instance
-
# attributes, but does so on a per-thread basis.
-
#
-
# So the values are scoped within the Thread.current space under the class name
-
# of the module.
-
1
class Module
-
# Defines a per-thread class attribute and creates class and instance reader methods.
-
# The underlying per-thread class variable is set to +nil+, if it is not previously defined.
-
#
-
# module Current
-
# thread_mattr_reader :user
-
# end
-
#
-
# Current.user # => nil
-
# Thread.current[:attr_Current_user] = "DHH"
-
# Current.user # => "DHH"
-
#
-
# The attribute name must be a valid method name in Ruby.
-
#
-
# module Foo
-
# thread_mattr_reader :"1_Badname"
-
# end
-
# # => NameError: invalid attribute name: 1_Badname
-
#
-
# To omit the instance reader method, pass
-
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
-
#
-
# class Current
-
# thread_mattr_reader :user, instance_reader: false
-
# end
-
#
-
# Current.new.user # => NoMethodError
-
1
def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc:
-
4
syms.each do |sym|
-
4
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
-
-
# The following generated method concatenates `name` because we want it
-
# to work with inheritance via polymorphism.
-
4
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}
-
Thread.current["attr_" + name + "_#{sym}"]
-
end
-
EOS
-
-
4
if instance_reader && instance_accessor
-
2
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}
-
self.class.#{sym}
-
end
-
EOS
-
end
-
-
4
Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
-
end
-
end
-
1
alias :thread_cattr_reader :thread_mattr_reader
-
-
# Defines a per-thread class attribute and creates a class and instance writer methods to
-
# allow assignment to the attribute.
-
#
-
# module Current
-
# thread_mattr_writer :user
-
# end
-
#
-
# Current.user = "DHH"
-
# Thread.current[:attr_Current_user] # => "DHH"
-
#
-
# To omit the instance writer method, pass
-
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
-
#
-
# class Current
-
# thread_mattr_writer :user, instance_writer: false
-
# end
-
#
-
# Current.new.user = "DHH" # => NoMethodError
-
1
def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc:
-
3
syms.each do |sym|
-
3
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
-
-
# The following generated method concatenates `name` because we want it
-
# to work with inheritance via polymorphism.
-
3
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def self.#{sym}=(obj)
-
Thread.current["attr_" + name + "_#{sym}"] = obj
-
end
-
EOS
-
-
3
if instance_writer && instance_accessor
-
1
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
-
def #{sym}=(obj)
-
self.class.#{sym} = obj
-
end
-
EOS
-
end
-
-
3
public_send("#{sym}=", default) unless default.nil?
-
end
-
end
-
1
alias :thread_cattr_writer :thread_mattr_writer
-
-
# Defines both class and instance accessors for class attributes.
-
#
-
# class Account
-
# thread_mattr_accessor :user
-
# end
-
#
-
# Account.user = "DHH"
-
# Account.user # => "DHH"
-
# Account.new.user # => "DHH"
-
#
-
# If a subclass changes the value, the parent class' value is not changed.
-
# Similarly, if the parent class changes the value, the value of subclasses
-
# is not changed.
-
#
-
# class Customer < Account
-
# end
-
#
-
# Customer.user = "Rafael"
-
# Customer.user # => "Rafael"
-
# Account.user # => "DHH"
-
#
-
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
-
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
-
#
-
# class Current
-
# thread_mattr_accessor :user, instance_writer: false, instance_reader: false
-
# end
-
#
-
# Current.new.user = "DHH" # => NoMethodError
-
# Current.new.user # => NoMethodError
-
#
-
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
-
#
-
# class Current
-
# thread_mattr_accessor :user, instance_accessor: false
-
# end
-
#
-
# Current.new.user = "DHH" # => NoMethodError
-
# Current.new.user # => NoMethodError
-
1
def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
-
3
thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
-
3
thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
-
end
-
1
alias :thread_cattr_accessor :thread_mattr_accessor
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/concern"
-
-
1
class Module
-
# = Bite-sized separation of concerns
-
#
-
# We often find ourselves with a medium-sized chunk of behavior that we'd
-
# like to extract, but only mix in to a single class.
-
#
-
# Extracting a plain old Ruby object to encapsulate it and collaborate or
-
# delegate to the original object is often a good choice, but when there's
-
# no additional state to encapsulate or we're making DSL-style declarations
-
# about the parent class, introducing new collaborators can obfuscate rather
-
# than simplify.
-
#
-
# The typical route is to just dump everything in a monolithic class, perhaps
-
# with a comment, as a least-bad alternative. Using modules in separate files
-
# means tedious sifting to get a big-picture view.
-
#
-
# = Dissatisfying ways to separate small concerns
-
#
-
# == Using comments:
-
#
-
# class Todo < ApplicationRecord
-
# # Other todo implementation
-
# # ...
-
#
-
# ## Event tracking
-
# has_many :events
-
#
-
# before_create :track_creation
-
#
-
# private
-
# def track_creation
-
# # ...
-
# end
-
# end
-
#
-
# == With an inline module:
-
#
-
# Noisy syntax.
-
#
-
# class Todo < ApplicationRecord
-
# # Other todo implementation
-
# # ...
-
#
-
# module EventTracking
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# has_many :events
-
# before_create :track_creation
-
# end
-
#
-
# private
-
# def track_creation
-
# # ...
-
# end
-
# end
-
# include EventTracking
-
# end
-
#
-
# == Mix-in noise exiled to its own file:
-
#
-
# Once our chunk of behavior starts pushing the scroll-to-understand-it
-
# boundary, we give in and move it to a separate file. At this size, the
-
# increased overhead can be a reasonable tradeoff even if it reduces our
-
# at-a-glance perception of how things work.
-
#
-
# class Todo < ApplicationRecord
-
# # Other todo implementation
-
# # ...
-
#
-
# include TodoEventTracking
-
# end
-
#
-
# = Introducing Module#concerning
-
#
-
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
-
# separate bite-sized concerns.
-
#
-
# class Todo < ApplicationRecord
-
# # Other todo implementation
-
# # ...
-
#
-
# concerning :EventTracking do
-
# included do
-
# has_many :events
-
# before_create :track_creation
-
# end
-
#
-
# private
-
# def track_creation
-
# # ...
-
# end
-
# end
-
# end
-
#
-
# Todo.ancestors
-
# # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
-
#
-
# This small step has some wonderful ripple effects. We can
-
# * grok the behavior of our class in one glance,
-
# * clean up monolithic junk-drawer classes by separating their concerns, and
-
# * stop leaning on protected/private for crude "this is internal stuff" modularity.
-
#
-
# === Prepending `concerning`
-
#
-
# `concerning` supports a `prepend: true` argument which will `prepend` the
-
# concern instead of using `include` for it.
-
1
module Concerning
-
# Define a new concern and mix it in.
-
1
def concerning(topic, prepend: false, &block)
-
2
method = prepend ? :prepend : :include
-
2
__send__(method, concern(topic, &block))
-
end
-
-
# A low-cruft shortcut to define a concern.
-
#
-
# concern :EventTracking do
-
# ...
-
# end
-
#
-
# is equivalent to
-
#
-
# module EventTracking
-
# extend ActiveSupport::Concern
-
#
-
# ...
-
# end
-
1
def concern(topic, &module_definition)
-
2
const_set topic, Module.new {
-
2
extend ::ActiveSupport::Concern
-
2
module_eval(&module_definition)
-
}
-
end
-
end
-
1
include Concerning
-
end
-
# frozen_string_literal: true
-
-
24
require "set"
-
-
24
class Module
-
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
-
# option is not used.
-
24
class DelegationError < NoMethodError; end
-
-
24
RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
-
else elsif END end ensure false for if in module next nil not or redo rescue retry
-
return self super then true undef unless until when while yield)
-
24
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
-
24
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
-
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
-
).freeze
-
-
# Provides a +delegate+ class method to easily expose contained objects'
-
# public methods as your own.
-
#
-
# ==== Options
-
# * <tt>:to</tt> - Specifies the target object name as a symbol or string
-
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
-
# * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+
-
# from being raised
-
# * <tt>:private</tt> - If set to true, changes method visibility to private
-
#
-
# The macro receives one or more method names (specified as symbols or
-
# strings) and the name of the target object via the <tt>:to</tt> option
-
# (also a symbol or string).
-
#
-
# Delegation is particularly useful with Active Record associations:
-
#
-
# class Greeter < ActiveRecord::Base
-
# def hello
-
# 'hello'
-
# end
-
#
-
# def goodbye
-
# 'goodbye'
-
# end
-
# end
-
#
-
# class Foo < ActiveRecord::Base
-
# belongs_to :greeter
-
# delegate :hello, to: :greeter
-
# end
-
#
-
# Foo.new.hello # => "hello"
-
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
-
#
-
# Multiple delegates to the same target are allowed:
-
#
-
# class Foo < ActiveRecord::Base
-
# belongs_to :greeter
-
# delegate :hello, :goodbye, to: :greeter
-
# end
-
#
-
# Foo.new.goodbye # => "goodbye"
-
#
-
# Methods can be delegated to instance variables, class variables, or constants
-
# by providing them as a symbols:
-
#
-
# class Foo
-
# CONSTANT_ARRAY = [0,1,2,3]
-
# @@class_array = [4,5,6,7]
-
#
-
# def initialize
-
# @instance_array = [8,9,10,11]
-
# end
-
# delegate :sum, to: :CONSTANT_ARRAY
-
# delegate :min, to: :@@class_array
-
# delegate :max, to: :@instance_array
-
# end
-
#
-
# Foo.new.sum # => 6
-
# Foo.new.min # => 4
-
# Foo.new.max # => 11
-
#
-
# It's also possible to delegate a method to the class by using +:class+:
-
#
-
# class Foo
-
# def self.hello
-
# "world"
-
# end
-
#
-
# delegate :hello, to: :class
-
# end
-
#
-
# Foo.new.hello # => "world"
-
#
-
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
-
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
-
# delegated to.
-
#
-
# Person = Struct.new(:name, :address)
-
#
-
# class Invoice < Struct.new(:client)
-
# delegate :name, :address, to: :client, prefix: true
-
# end
-
#
-
# john_doe = Person.new('John Doe', 'Vimmersvej 13')
-
# invoice = Invoice.new(john_doe)
-
# invoice.client_name # => "John Doe"
-
# invoice.client_address # => "Vimmersvej 13"
-
#
-
# It is also possible to supply a custom prefix.
-
#
-
# class Invoice < Struct.new(:client)
-
# delegate :name, :address, to: :client, prefix: :customer
-
# end
-
#
-
# invoice = Invoice.new(john_doe)
-
# invoice.customer_name # => 'John Doe'
-
# invoice.customer_address # => 'Vimmersvej 13'
-
#
-
# The delegated methods are public by default.
-
# Pass <tt>private: true</tt> to change that.
-
#
-
# class User < ActiveRecord::Base
-
# has_one :profile
-
# delegate :first_name, to: :profile
-
# delegate :date_of_birth, to: :profile, private: true
-
#
-
# def age
-
# Date.today.year - date_of_birth.year
-
# end
-
# end
-
#
-
# User.new.first_name # => "Tomas"
-
# User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
-
# User.new.age # => 2
-
#
-
# If the target is +nil+ and does not respond to the delegated method a
-
# +Module::DelegationError+ is raised. If you wish to instead return +nil+,
-
# use the <tt>:allow_nil</tt> option.
-
#
-
# class User < ActiveRecord::Base
-
# has_one :profile
-
# delegate :age, to: :profile
-
# end
-
#
-
# User.new.age
-
# # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
-
#
-
# But if not having a profile yet is fine and should not be an error
-
# condition:
-
#
-
# class User < ActiveRecord::Base
-
# has_one :profile
-
# delegate :age, to: :profile, allow_nil: true
-
# end
-
#
-
# User.new.age # nil
-
#
-
# Note that if the target is not +nil+ then the call is attempted regardless of the
-
# <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
-
# does not respond to the method:
-
#
-
# class Foo
-
# def initialize(bar)
-
# @bar = bar
-
# end
-
#
-
# delegate :name, to: :@bar, allow_nil: true
-
# end
-
#
-
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
-
#
-
# The target method must be public, otherwise it will raise +NoMethodError+.
-
24
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
-
558
unless to
-
raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
-
end
-
-
558
if prefix == true && /^[^a-z_]/.match?(to)
-
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
-
end
-
-
558
method_prefix = \
-
558
if prefix
-
6
"#{prefix == true ? to : prefix}_"
-
else
-
552
""
-
end
-
-
558
location = caller_locations(1, 1).first
-
558
file, line = location.path, location.lineno
-
-
558
to = to.to_s
-
558
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
-
-
558
method_def = []
-
558
method_names = []
-
-
558
methods.map do |method|
-
699
method_name = prefix ? "#{method_prefix}#{method}" : method
-
699
method_names << method_name.to_sym
-
-
# Attribute writer methods only accept one argument. Makes sure []=
-
# methods still accept two arguments.
-
699
definition = if /[^\]]=$/.match?(method)
-
170
"arg"
-
529
elsif RUBY_VERSION >= "2.7"
-
"..."
-
else
-
529
"*args, &block"
-
end
-
-
# The following generated method calls the target exactly once, storing
-
# the returned value in a dummy variable.
-
#
-
# Reason is twofold: On one hand doing less calls is in general better.
-
# On the other hand it could be that the target has side-effects,
-
# whereas conceptually, from the user point of view, the delegator should
-
# be doing one call.
-
699
if allow_nil
-
4
method = method.to_s
-
-
method_def <<
-
"def #{method_name}(#{definition})" <<
-
" _ = #{to}" <<
-
" if !_.nil? || nil.respond_to?(:#{method})" <<
-
" _.#{method}(#{definition})" <<
-
4
" end" <<
-
"end"
-
else
-
695
method = method.to_s
-
695
method_name = method_name.to_s
-
-
method_def <<
-
"def #{method_name}(#{definition})" <<
-
" _ = #{to}" <<
-
" _.#{method}(#{definition})" <<
-
"rescue NoMethodError => e" <<
-
" if _.nil? && e.name == :#{method}" <<
-
%( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") <<
-
" else" <<
-
" raise" <<
-
695
" end" <<
-
"end"
-
end
-
end
-
558
module_eval(method_def.join(";"), file, line)
-
558
private(*method_names) if private
-
558
method_names
-
end
-
-
# When building decorators, a common pattern may emerge:
-
#
-
# class Partition
-
# def initialize(event)
-
# @event = event
-
# end
-
#
-
# def person
-
# detail.person || creator
-
# end
-
#
-
# private
-
# def respond_to_missing?(name, include_private = false)
-
# @event.respond_to?(name, include_private)
-
# end
-
#
-
# def method_missing(method, *args, &block)
-
# @event.send(method, *args, &block)
-
# end
-
# end
-
#
-
# With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
-
#
-
# class Partition
-
# delegate_missing_to :@event
-
#
-
# def initialize(event)
-
# @event = event
-
# end
-
#
-
# def person
-
# detail.person || creator
-
# end
-
# end
-
#
-
# The target can be anything callable within the object, e.g. instance
-
# variables, methods, constants, etc.
-
#
-
# The delegated method must be public on the target, otherwise it will
-
# raise +DelegationError+. If you wish to instead return +nil+,
-
# use the <tt>:allow_nil</tt> option.
-
#
-
# The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
-
# delegation due to possible interference when calling
-
# <tt>Marshal.dump(object)</tt>, should the delegation target method
-
# of <tt>object</tt> add or remove instance variables.
-
24
def delegate_missing_to(target, allow_nil: nil)
-
6
target = target.to_s
-
6
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
-
-
6
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def respond_to_missing?(name, include_private = false)
-
# It may look like an oversight, but we deliberately do not pass
-
# +include_private+, because they do not get delegated.
-
-
return false if name == :marshal_dump || name == :_dump
-
#{target}.respond_to?(name) || super
-
end
-
-
def method_missing(method, *args, &block)
-
if #{target}.respond_to?(method)
-
#{target}.public_send(method, *args, &block)
-
else
-
begin
-
super
-
rescue NoMethodError
-
if #{target}.nil?
-
if #{allow_nil == true}
-
nil
-
else
-
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
-
end
-
else
-
raise
-
end
-
end
-
end
-
end
-
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
-
RUBY
-
end
-
end
-
# frozen_string_literal: true
-
-
23
class Module
-
# deprecate :foo
-
# deprecate bar: 'message'
-
# deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
-
#
-
# You can also use custom deprecator instance:
-
#
-
# deprecate :foo, deprecator: MyLib::Deprecator.new
-
# deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
-
#
-
# \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
-
# method where you can implement your custom warning behavior.
-
#
-
# class MyLib::Deprecator
-
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
-
# message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
-
# Kernel.warn message
-
# end
-
# end
-
23
def deprecate(*method_names)
-
4
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/filters"
-
3
require "active_support/inflector"
-
-
3
class Module
-
# Returns the name of the module containing this one.
-
#
-
# M::N.module_parent_name # => "M"
-
3
def module_parent_name
-
if defined?(@parent_name)
-
@parent_name
-
else
-
parent_name = name =~ /::[^:]+\z/ ? -$` : nil
-
@parent_name = parent_name unless frozen?
-
parent_name
-
end
-
end
-
-
3
def parent_name
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`Module#parent_name` has been renamed to `module_parent_name`.
-
`parent_name` is deprecated and will be removed in Rails 6.1.
-
MSG
-
module_parent_name
-
end
-
-
# Returns the module which contains this one according to its name.
-
#
-
# module M
-
# module N
-
# end
-
# end
-
# X = M::N
-
#
-
# M::N.module_parent # => M
-
# X.module_parent # => M
-
#
-
# The parent of top-level and anonymous modules is Object.
-
#
-
# M.module_parent # => Object
-
# Module.new.module_parent # => Object
-
3
def module_parent
-
module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object
-
end
-
-
3
def parent
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`Module#parent` has been renamed to `module_parent`.
-
`parent` is deprecated and will be removed in Rails 6.1.
-
MSG
-
module_parent
-
end
-
-
# Returns all the parents of this module according to its name, ordered from
-
# nested outwards. The receiver is not contained within the result.
-
#
-
# module M
-
# module N
-
# end
-
# end
-
# X = M::N
-
#
-
# M.module_parents # => [Object]
-
# M::N.module_parents # => [M, Object]
-
# X.module_parents # => [M, Object]
-
3
def module_parents
-
parents = []
-
if module_parent_name
-
parts = module_parent_name.split("::")
-
until parts.empty?
-
parents << ActiveSupport::Inflector.constantize(parts * "::")
-
parts.pop
-
end
-
end
-
parents << Object unless parents.include? Object
-
parents
-
end
-
-
3
def parents
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`Module#parents` has been renamed to `module_parents`.
-
`parents` is deprecated and will be removed in Rails 6.1.
-
MSG
-
module_parents
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/module/anonymous"
-
require "active_support/core_ext/string/inflections"
-
-
ActiveSupport::Deprecation.warn("reachable is deprecated and will be removed from the framework.")
-
# frozen_string_literal: true
-
-
23
class Module
-
# Marks the named method as intended to be redefined, if it exists.
-
# Suppresses the Ruby method redefinition warning. Prefer
-
# #redefine_method where possible.
-
23
def silence_redefinition_of_method(method)
-
966
if method_defined?(method) || private_method_defined?(method)
-
# This suppresses the "method redefined" warning; the self-alias
-
# looks odd, but means we don't need to generate a unique name
-
916
alias_method method, method
-
end
-
end
-
-
# Replaces the existing method definition, if there is one, with the passed
-
# block as its body.
-
23
def redefine_method(method, &block)
-
340
visibility = method_visibility(method)
-
340
silence_redefinition_of_method(method)
-
340
define_method(method, &block)
-
340
send(visibility, method)
-
end
-
-
# Replaces the existing singleton method definition, if there is one, with
-
# the passed block as its body.
-
23
def redefine_singleton_method(method, &block)
-
332
singleton_class.redefine_method(method, &block)
-
end
-
-
23
def method_visibility(method) # :nodoc:
-
case
-
when private_method_defined?(method)
-
:private
-
when protected_method_defined?(method)
-
:protected
-
else
-
340
:public
-
340
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/module/redefine_method"
-
-
1
class Module
-
# Removes the named method, if it exists.
-
1
def remove_possible_method(method)
-
if method_defined?(method) || private_method_defined?(method)
-
undef_method(method)
-
end
-
end
-
-
# Removes the named singleton method, if it exists.
-
1
def remove_possible_singleton_method(method)
-
singleton_class.remove_possible_method(method)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
class NameError
-
# Extract the name of the missing constant from the exception message.
-
#
-
# begin
-
# HelloWorld
-
# rescue NameError => e
-
# e.missing_name
-
# end
-
# # => "HelloWorld"
-
3
def missing_name
-
# Since ruby v2.3.0 `did_you_mean` gem is loaded by default.
-
# It extends NameError#message with spell corrections which are SLOW.
-
# We should use original_message message instead.
-
message = respond_to?(:original_message) ? original_message : self.message
-
return unless message.start_with?("uninitialized constant ")
-
-
receiver = begin
-
self.receiver
-
rescue ArgumentError
-
nil
-
end
-
-
if receiver == Object
-
name.to_s
-
elsif receiver
-
"#{real_mod_name(receiver)}::#{self.name}"
-
else
-
if match = message.match(/((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/)
-
match[1]
-
end
-
end
-
end
-
-
# Was this exception raised because the given name was missing?
-
#
-
# begin
-
# HelloWorld
-
# rescue NameError => e
-
# e.missing_name?("HelloWorld")
-
# end
-
# # => true
-
3
def missing_name?(name)
-
if name.is_a? Symbol
-
self.name == name
-
else
-
missing_name == name.to_s
-
end
-
end
-
-
3
private
-
3
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
-
3
private_constant :UNBOUND_METHOD_MODULE_NAME
-
-
3
if UnboundMethod.method_defined?(:bind_call)
-
def real_mod_name(mod)
-
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
-
end
-
else
-
3
def real_mod_name(mod)
-
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/numeric/bytes"
-
1
require "active_support/core_ext/numeric/time"
-
1
require "active_support/core_ext/numeric/conversions"
-
# frozen_string_literal: true
-
-
13
class Numeric
-
13
KILOBYTE = 1024
-
13
MEGABYTE = KILOBYTE * 1024
-
13
GIGABYTE = MEGABYTE * 1024
-
13
TERABYTE = GIGABYTE * 1024
-
13
PETABYTE = TERABYTE * 1024
-
13
EXABYTE = PETABYTE * 1024
-
-
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
-
#
-
# 2.bytes # => 2
-
13
def bytes
-
self
-
end
-
13
alias :byte :bytes
-
-
# Returns the number of bytes equivalent to the kilobytes provided.
-
#
-
# 2.kilobytes # => 2048
-
13
def kilobytes
-
20
self * KILOBYTE
-
end
-
13
alias :kilobyte :kilobytes
-
-
# Returns the number of bytes equivalent to the megabytes provided.
-
#
-
# 2.megabytes # => 2_097_152
-
13
def megabytes
-
self * MEGABYTE
-
end
-
13
alias :megabyte :megabytes
-
-
# Returns the number of bytes equivalent to the gigabytes provided.
-
#
-
# 2.gigabytes # => 2_147_483_648
-
13
def gigabytes
-
self * GIGABYTE
-
end
-
13
alias :gigabyte :gigabytes
-
-
# Returns the number of bytes equivalent to the terabytes provided.
-
#
-
# 2.terabytes # => 2_199_023_255_552
-
13
def terabytes
-
self * TERABYTE
-
end
-
13
alias :terabyte :terabytes
-
-
# Returns the number of bytes equivalent to the petabytes provided.
-
#
-
# 2.petabytes # => 2_251_799_813_685_248
-
13
def petabytes
-
self * PETABYTE
-
end
-
13
alias :petabyte :petabytes
-
-
# Returns the number of bytes equivalent to the exabytes provided.
-
#
-
# 2.exabytes # => 2_305_843_009_213_693_952
-
13
def exabytes
-
self * EXABYTE
-
end
-
13
alias :exabyte :exabytes
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/big_decimal/conversions"
-
1
require "active_support/number_helper"
-
1
require "active_support/core_ext/module/deprecation"
-
-
1
module ActiveSupport
-
1
module NumericWithFormat
-
# Provides options for converting numbers into formatted strings.
-
# Options are provided for phone numbers, currency, percentage,
-
# precision, positional notation, file size and pretty printing.
-
#
-
# ==== Options
-
#
-
# For details on which formats use which options, see ActiveSupport::NumberHelper
-
#
-
# ==== Examples
-
#
-
# Phone Numbers:
-
# 5551234.to_s(:phone) # => "555-1234"
-
# 1235551234.to_s(:phone) # => "123-555-1234"
-
# 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
-
# 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
-
# 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
-
# 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
-
# 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
-
# # => "+1.123.555.1234 x 1343"
-
#
-
# Currency:
-
# 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
-
# 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
-
# 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
-
# 1234567890.506.to_s(:currency, round_mode: :down) # => "$1,234,567,890.50"
-
# 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
-
# -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
-
# # => "($1,234,567,890.50)"
-
# 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '')
-
# # => "£1234567890,50"
-
# 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u')
-
# # => "1234567890,50 £"
-
#
-
# Percentage:
-
# 100.to_s(:percentage) # => "100.000%"
-
# 100.to_s(:percentage, precision: 0) # => "100%"
-
# 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
-
# 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
-
# 302.24398923423.to_s(:percentage, round_mode: :down) # => "302.243%"
-
# 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
-
# 100.to_s(:percentage, format: '%n %') # => "100.000 %"
-
#
-
# Delimited:
-
# 12345678.to_s(:delimited) # => "12,345,678"
-
# 12345678.05.to_s(:delimited) # => "12,345,678.05"
-
# 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
-
# 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
-
# 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
-
# 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
-
# 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
-
# # => "98 765 432,98"
-
#
-
# Rounded:
-
# 111.2345.to_s(:rounded) # => "111.235"
-
# 111.2345.to_s(:rounded, precision: 2) # => "111.23"
-
# 111.2345.to_s(:rounded, precision: 2, round_mode: :up) # => "111.24"
-
# 13.to_s(:rounded, precision: 5) # => "13.00000"
-
# 389.32314.to_s(:rounded, precision: 0) # => "389"
-
# 111.2345.to_s(:rounded, significant: true) # => "111"
-
# 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
-
# 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
-
# 111.234.to_s(:rounded, locale: :fr) # => "111,234"
-
# 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
-
# # => "13"
-
# 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
-
# 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
-
# # => "1.111,23"
-
#
-
# Human-friendly size in Bytes:
-
# 123.to_s(:human_size) # => "123 Bytes"
-
# 1234.to_s(:human_size) # => "1.21 KB"
-
# 12345.to_s(:human_size) # => "12.1 KB"
-
# 1234567.to_s(:human_size) # => "1.18 MB"
-
# 1234567890.to_s(:human_size) # => "1.15 GB"
-
# 1234567890123.to_s(:human_size) # => "1.12 TB"
-
# 1234567890123456.to_s(:human_size) # => "1.1 PB"
-
# 1234567890123456789.to_s(:human_size) # => "1.07 EB"
-
# 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
-
# 1234567.to_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB"
-
# 483989.to_s(:human_size, precision: 2) # => "470 KB"
-
# 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
-
# 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
-
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
-
#
-
# Human-friendly format:
-
# 123.to_s(:human) # => "123"
-
# 1234.to_s(:human) # => "1.23 Thousand"
-
# 12345.to_s(:human) # => "12.3 Thousand"
-
# 1234567.to_s(:human) # => "1.23 Million"
-
# 1234567890.to_s(:human) # => "1.23 Billion"
-
# 1234567890123.to_s(:human) # => "1.23 Trillion"
-
# 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
-
# 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
-
# 489939.to_s(:human, precision: 2) # => "490 Thousand"
-
# 489939.to_s(:human, precision: 2, round_mode: :down) # => "480 Thousand"
-
# 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
-
# 1234567.to_s(:human, precision: 4,
-
# significant: false) # => "1.2346 Million"
-
# 1234567.to_s(:human, precision: 1,
-
# separator: ',',
-
# significant: false) # => "1,2 Million"
-
1
def to_s(format = nil, options = nil)
-
4699
case format
-
when nil
-
4699
super()
-
when Integer, String
-
super(format)
-
when :phone
-
ActiveSupport::NumberHelper.number_to_phone(self, options || {})
-
when :currency
-
ActiveSupport::NumberHelper.number_to_currency(self, options || {})
-
when :percentage
-
ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
-
when :delimited
-
ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
-
when :rounded
-
ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
-
when :human
-
ActiveSupport::NumberHelper.number_to_human(self, options || {})
-
when :human_size
-
ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
-
when Symbol
-
super()
-
else
-
super(format)
-
end
-
end
-
end
-
end
-
-
1
Integer.prepend ActiveSupport::NumericWithFormat
-
1
Float.prepend ActiveSupport::NumericWithFormat
-
1
BigDecimal.prepend ActiveSupport::NumericWithFormat
-
# frozen_string_literal: true
-
-
require "active_support/deprecation"
-
-
ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
-
# frozen_string_literal: true
-
-
14
require "active_support/duration"
-
14
require "active_support/core_ext/time/calculations"
-
14
require "active_support/core_ext/time/acts_like"
-
14
require "active_support/core_ext/date/calculations"
-
14
require "active_support/core_ext/date/acts_like"
-
-
14
class Numeric
-
# Returns a Duration instance matching the number of seconds provided.
-
#
-
# 2.seconds # => 2 seconds
-
14
def seconds
-
ActiveSupport::Duration.seconds(self)
-
end
-
14
alias :second :seconds
-
-
# Returns a Duration instance matching the number of minutes provided.
-
#
-
# 2.minutes # => 2 minutes
-
14
def minutes
-
ActiveSupport::Duration.minutes(self)
-
end
-
14
alias :minute :minutes
-
-
# Returns a Duration instance matching the number of hours provided.
-
#
-
# 2.hours # => 2 hours
-
14
def hours
-
ActiveSupport::Duration.hours(self)
-
end
-
14
alias :hour :hours
-
-
# Returns a Duration instance matching the number of days provided.
-
#
-
# 2.days # => 2 days
-
14
def days
-
ActiveSupport::Duration.days(self)
-
end
-
14
alias :day :days
-
-
# Returns a Duration instance matching the number of weeks provided.
-
#
-
# 2.weeks # => 2 weeks
-
14
def weeks
-
ActiveSupport::Duration.weeks(self)
-
end
-
14
alias :week :weeks
-
-
# Returns a Duration instance matching the number of fortnights provided.
-
#
-
# 2.fortnights # => 4 weeks
-
14
def fortnights
-
ActiveSupport::Duration.weeks(self * 2)
-
end
-
14
alias :fortnight :fortnights
-
-
# Returns the number of milliseconds equivalent to the seconds provided.
-
# Used with the standard time durations.
-
#
-
# 2.in_milliseconds # => 2000
-
# 1.hour.in_milliseconds # => 3600000
-
14
def in_milliseconds
-
self * 1000
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/object/acts_like"
-
1
require "active_support/core_ext/object/blank"
-
1
require "active_support/core_ext/object/duplicable"
-
1
require "active_support/core_ext/object/deep_dup"
-
1
require "active_support/core_ext/object/try"
-
1
require "active_support/core_ext/object/inclusion"
-
-
1
require "active_support/core_ext/object/conversions"
-
1
require "active_support/core_ext/object/instance_variables"
-
-
1
require "active_support/core_ext/object/json"
-
1
require "active_support/core_ext/object/to_param"
-
1
require "active_support/core_ext/object/to_query"
-
1
require "active_support/core_ext/object/with_options"
-
# frozen_string_literal: true
-
-
23
class Object
-
# A duck-type assistant method. For example, Active Support extends Date
-
# to define an <tt>acts_like_date?</tt> method, and extends Time to define
-
# <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and
-
# <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
-
# we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
-
23
def acts_like?(duck)
-
case duck
-
when :time
-
respond_to? :acts_like_time?
-
when :date
-
respond_to? :acts_like_date?
-
when :string
-
respond_to? :acts_like_string?
-
else
-
respond_to? :"acts_like_#{duck}?"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "concurrent/map"
-
-
24
class Object
-
# An object is blank if it's false, empty, or a whitespace string.
-
# For example, +nil+, '', ' ', [], {}, and +false+ are all blank.
-
#
-
# This simplifies
-
#
-
# !address || address.empty?
-
#
-
# to
-
#
-
# address.blank?
-
#
-
# @return [true, false]
-
24
def blank?
-
34
respond_to?(:empty?) ? !!empty? : !self
-
end
-
-
# An object is present if it's not blank.
-
#
-
# @return [true, false]
-
24
def present?
-
!blank?
-
end
-
-
# Returns the receiver if it's present otherwise returns +nil+.
-
# <tt>object.presence</tt> is equivalent to
-
#
-
# object.present? ? object : nil
-
#
-
# For example, something like
-
#
-
# state = params[:state] if params[:state].present?
-
# country = params[:country] if params[:country].present?
-
# region = state || country || 'US'
-
#
-
# becomes
-
#
-
# region = params[:state].presence || params[:country].presence || 'US'
-
#
-
# @return [Object]
-
24
def presence
-
self if present?
-
end
-
end
-
-
24
class NilClass
-
# +nil+ is blank:
-
#
-
# nil.blank? # => true
-
#
-
# @return [true]
-
24
def blank?
-
248
true
-
end
-
end
-
-
24
class FalseClass
-
# +false+ is blank:
-
#
-
# false.blank? # => true
-
#
-
# @return [true]
-
24
def blank?
-
true
-
end
-
end
-
-
24
class TrueClass
-
# +true+ is not blank:
-
#
-
# true.blank? # => false
-
#
-
# @return [false]
-
24
def blank?
-
false
-
end
-
end
-
-
24
class Array
-
# An array is blank if it's empty:
-
#
-
# [].blank? # => true
-
# [1,2,3].blank? # => false
-
#
-
# @return [true, false]
-
24
alias_method :blank?, :empty?
-
end
-
-
24
class Hash
-
# A hash is blank if it's empty:
-
#
-
# {}.blank? # => true
-
# { key: 'value' }.blank? # => false
-
#
-
# @return [true, false]
-
24
alias_method :blank?, :empty?
-
end
-
-
24
class String
-
24
BLANK_RE = /\A[[:space:]]*\z/
-
24
ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
-
h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
-
end
-
-
# A string is blank if it's empty or contains whitespaces only:
-
#
-
# ''.blank? # => true
-
# ' '.blank? # => true
-
# "\t\n\r".blank? # => true
-
# ' blah '.blank? # => false
-
#
-
# Unicode whitespace is supported:
-
#
-
# "\u00a0".blank? # => true
-
#
-
# @return [true, false]
-
24
def blank?
-
# The regexp that matches blank strings is expensive. For the case of empty
-
# strings we can speed up this method (~3.5x) with an empty? call. The
-
# penalty for the rest of strings is marginal.
-
empty? ||
-
begin
-
BLANK_RE.match?(self)
-
rescue Encoding::CompatibilityError
-
ENCODED_BLANKS[self.encoding].match?(self)
-
end
-
end
-
end
-
-
24
class Numeric #:nodoc:
-
# No number is blank:
-
#
-
# 1.blank? # => false
-
# 0.blank? # => false
-
#
-
# @return [false]
-
24
def blank?
-
false
-
end
-
end
-
-
24
class Time #:nodoc:
-
# No Time is blank:
-
#
-
# Time.now.blank? # => false
-
#
-
# @return [false]
-
24
def blank?
-
false
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/object/to_param"
-
1
require "active_support/core_ext/object/to_query"
-
1
require "active_support/core_ext/array/conversions"
-
1
require "active_support/core_ext/hash/conversions"
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/object/duplicable"
-
-
1
class Object
-
# Returns a deep copy of object if it's duplicable. If it's
-
# not duplicable, returns +self+.
-
#
-
# object = Object.new
-
# dup = object.deep_dup
-
# dup.instance_variable_set(:@a, 1)
-
#
-
# object.instance_variable_defined?(:@a) # => false
-
# dup.instance_variable_defined?(:@a) # => true
-
1
def deep_dup
-
duplicable? ? dup : self
-
end
-
end
-
-
1
class Array
-
# Returns a deep copy of array.
-
#
-
# array = [1, [2, 3]]
-
# dup = array.deep_dup
-
# dup[1][2] = 4
-
#
-
# array[1][2] # => nil
-
# dup[1][2] # => 4
-
1
def deep_dup
-
map(&:deep_dup)
-
end
-
end
-
-
1
class Hash
-
# Returns a deep copy of hash.
-
#
-
# hash = { a: { b: 'b' } }
-
# dup = hash.deep_dup
-
# dup[:a][:c] = 'c'
-
#
-
# hash[:a][:c] # => nil
-
# dup[:a][:c] # => "c"
-
1
def deep_dup
-
hash = dup
-
each_pair do |key, value|
-
if key.frozen? && ::String === key
-
hash[key] = value.deep_dup
-
else
-
hash.delete(key)
-
hash[key.deep_dup] = value.deep_dup
-
end
-
end
-
hash
-
end
-
end
-
# frozen_string_literal: true
-
-
#--
-
# Most objects are cloneable, but not all. For example you can't dup methods:
-
#
-
# method(:puts).dup # => TypeError: allocator undefined for Method
-
#
-
# Classes may signal their instances are not duplicable removing +dup+/+clone+
-
# or raising exceptions from them. So, to dup an arbitrary object you normally
-
# use an optimistic approach and are ready to catch an exception, say:
-
#
-
# arbitrary_object.dup rescue object
-
#
-
# Rails dups objects in a few critical spots where they are not that arbitrary.
-
# That rescue is very expensive (like 40 times slower than a predicate), and it
-
# is often triggered.
-
#
-
# That's why we hardcode the following cases and check duplicable? instead of
-
# using that rescue idiom.
-
#++
-
1
class Object
-
# Can you safely dup this object?
-
#
-
# False for method objects;
-
# true otherwise.
-
1
def duplicable?
-
true
-
end
-
end
-
-
1
class Method
-
# Methods are not duplicable:
-
#
-
# method(:puts).duplicable? # => false
-
# method(:puts).dup # => TypeError: allocator undefined for Method
-
1
def duplicable?
-
false
-
end
-
end
-
-
1
class UnboundMethod
-
# Unbound methods are not duplicable:
-
#
-
# method(:puts).unbind.duplicable? # => false
-
# method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod
-
1
def duplicable?
-
false
-
end
-
end
-
# frozen_string_literal: true
-
-
2
class Object
-
# Returns true if this object is included in the argument. Argument must be
-
# any object which responds to +#include?+. Usage:
-
#
-
# characters = ["Konata", "Kagami", "Tsukasa"]
-
# "Konata".in?(characters) # => true
-
#
-
# This will throw an +ArgumentError+ if the argument doesn't respond
-
# to +#include?+.
-
2
def in?(another_object)
-
another_object.include?(self)
-
rescue NoMethodError
-
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
-
end
-
-
# Returns the receiver if it's included in the argument otherwise returns +nil+.
-
# Argument must be any object which responds to +#include?+. Usage:
-
#
-
# params[:bucket_type].presence_in %w( project calendar )
-
#
-
# This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
-
#
-
# @return [Object]
-
2
def presence_in(another_object)
-
in?(another_object) ? self : nil
-
end
-
end
-
# frozen_string_literal: true
-
-
2
class Object
-
# Returns a hash with string keys that maps instance variable names without "@" to their
-
# corresponding values.
-
#
-
# class C
-
# def initialize(x, y)
-
# @x, @y = x, y
-
# end
-
# end
-
#
-
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
-
2
def instance_values
-
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
-
end
-
-
# Returns an array of instance variable names as strings including "@".
-
#
-
# class C
-
# def initialize(x, y)
-
# @x, @y = x, y
-
# end
-
# end
-
#
-
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
-
2
def instance_variable_names
-
instance_variables.map(&:to_s)
-
end
-
end
-
# frozen_string_literal: true
-
-
# Hack to load json gem first so we can overwrite its to_json.
-
2
require "json"
-
2
require "bigdecimal"
-
2
require "uri/generic"
-
2
require "pathname"
-
2
require "active_support/core_ext/big_decimal/conversions" # for #to_s
-
2
require "active_support/core_ext/hash/except"
-
2
require "active_support/core_ext/hash/slice"
-
2
require "active_support/core_ext/object/instance_variables"
-
2
require "time"
-
2
require "active_support/core_ext/time/conversions"
-
2
require "active_support/core_ext/date_time/conversions"
-
2
require "active_support/core_ext/date/conversions"
-
-
#--
-
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
-
# their default behavior. That said, we need to define the basic to_json method in all of them,
-
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
-
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
-
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-
#
-
# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the
-
# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always
-
# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the
-
# calls to the original to_json method.
-
#
-
# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is
-
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
-
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
-
# should give exactly the same results with or without active support.
-
-
2
module ActiveSupport
-
2
module ToJsonWithActiveSupportEncoder # :nodoc:
-
2
def to_json(options = nil)
-
if options.is_a?(::JSON::State)
-
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
-
super(options)
-
else
-
# to_json is being invoked directly, use ActiveSupport's encoder
-
ActiveSupport::JSON.encode(self, options)
-
end
-
end
-
end
-
end
-
-
2
[Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].reverse_each do |klass|
-
20
klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder)
-
end
-
-
2
class Object
-
2
def as_json(options = nil) #:nodoc:
-
if respond_to?(:to_hash)
-
to_hash.as_json(options)
-
else
-
instance_values.as_json(options)
-
end
-
end
-
end
-
-
2
class Struct #:nodoc:
-
2
def as_json(options = nil)
-
Hash[members.zip(values)].as_json(options)
-
end
-
end
-
-
2
class TrueClass
-
2
def as_json(options = nil) #:nodoc:
-
self
-
end
-
end
-
-
2
class FalseClass
-
2
def as_json(options = nil) #:nodoc:
-
self
-
end
-
end
-
-
2
class NilClass
-
2
def as_json(options = nil) #:nodoc:
-
self
-
end
-
end
-
-
2
class String
-
2
def as_json(options = nil) #:nodoc:
-
self
-
end
-
end
-
-
2
class Symbol
-
2
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
2
class Numeric
-
2
def as_json(options = nil) #:nodoc:
-
self
-
end
-
end
-
-
2
class Float
-
# Encoding Infinity or NaN to JSON should return "null". The default returns
-
# "Infinity" or "NaN" which are not valid JSON.
-
2
def as_json(options = nil) #:nodoc:
-
finite? ? self : nil
-
end
-
end
-
-
2
class BigDecimal
-
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
-
# however, parse non-integer JSON numbers directly as floats. Clients using
-
# those libraries would get in general a wrong number and no way to recover
-
# other than manually inspecting the string with the JSON code itself.
-
#
-
# That's why a JSON string is returned. The JSON literal is not numeric, but
-
# if the other end knows by contract that the data is supposed to be a
-
# BigDecimal, it still has the chance to post-process the string and get the
-
# real value.
-
2
def as_json(options = nil) #:nodoc:
-
finite? ? to_s : nil
-
end
-
end
-
-
2
class Regexp
-
2
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
2
module Enumerable
-
2
def as_json(options = nil) #:nodoc:
-
to_a.as_json(options)
-
end
-
end
-
-
2
class IO
-
2
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
2
class Range
-
2
def as_json(options = nil) #:nodoc:
-
to_s
-
end
-
end
-
-
2
class Array
-
2
def as_json(options = nil) #:nodoc:
-
map { |v| options ? v.as_json(options.dup) : v.as_json }
-
end
-
end
-
-
2
class Hash
-
2
def as_json(options = nil) #:nodoc:
-
# create a subset of the hash by applying :only or :except
-
subset = if options
-
if attrs = options[:only]
-
slice(*Array(attrs))
-
elsif attrs = options[:except]
-
except(*Array(attrs))
-
else
-
self
-
end
-
else
-
self
-
end
-
-
result = {}
-
subset.each do |k, v|
-
result[k.to_s] = options ? v.as_json(options.dup) : v.as_json
-
end
-
result
-
end
-
end
-
-
2
class Time
-
2
def as_json(options = nil) #:nodoc:
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
xmlschema(ActiveSupport::JSON::Encoding.time_precision)
-
else
-
%(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
-
end
-
end
-
end
-
-
2
class Date
-
2
def as_json(options = nil) #:nodoc:
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
strftime("%Y-%m-%d")
-
else
-
strftime("%Y/%m/%d")
-
end
-
end
-
end
-
-
2
class DateTime
-
2
def as_json(options = nil) #:nodoc:
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
xmlschema(ActiveSupport::JSON::Encoding.time_precision)
-
else
-
strftime("%Y/%m/%d %H:%M:%S %z")
-
end
-
end
-
end
-
-
2
class URI::Generic #:nodoc:
-
2
def as_json(options = nil)
-
to_s
-
end
-
end
-
-
2
class Pathname #:nodoc:
-
2
def as_json(options = nil)
-
to_s
-
end
-
end
-
-
2
class Process::Status #:nodoc:
-
2
def as_json(options = nil)
-
{ exitstatus: exitstatus, pid: pid }
-
end
-
end
-
-
2
class Exception
-
2
def as_json(options = nil)
-
to_s
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/object/to_query"
-
# frozen_string_literal: true
-
-
23
require "cgi"
-
-
23
class Object
-
# Alias of <tt>to_s</tt>.
-
23
def to_param
-
to_s
-
end
-
-
# Converts an object into a string suitable for use as a URL query string,
-
# using the given <tt>key</tt> as the param name.
-
23
def to_query(key)
-
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
-
end
-
end
-
-
23
class NilClass
-
# Returns +self+.
-
23
def to_param
-
self
-
end
-
end
-
-
23
class TrueClass
-
# Returns +self+.
-
23
def to_param
-
self
-
end
-
end
-
-
23
class FalseClass
-
# Returns +self+.
-
23
def to_param
-
self
-
end
-
end
-
-
23
class Array
-
# Calls <tt>to_param</tt> on all its elements and joins the result with
-
# slashes. This is used by <tt>url_for</tt> in Action Pack.
-
23
def to_param
-
collect(&:to_param).join "/"
-
end
-
-
# Converts an array into a string suitable for use as a URL query string,
-
# using the given +key+ as the param name.
-
#
-
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
-
23
def to_query(key)
-
prefix = "#{key}[]"
-
-
if empty?
-
nil.to_query(prefix)
-
else
-
collect { |value| value.to_query(prefix) }.join "&"
-
end
-
end
-
end
-
-
23
class Hash
-
# Returns a string representation of the receiver suitable for use as a URL
-
# query string:
-
#
-
# {name: 'David', nationality: 'Danish'}.to_query
-
# # => "name=David&nationality=Danish"
-
#
-
# An optional namespace can be passed to enclose key names:
-
#
-
# {name: 'David', nationality: 'Danish'}.to_query('user')
-
# # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
-
#
-
# The string pairs "key=value" that conform the query string
-
# are sorted lexicographically in ascending order.
-
#
-
# This method is also aliased as +to_param+.
-
23
def to_query(namespace = nil)
-
query = collect do |key, value|
-
unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
-
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
-
end
-
end.compact
-
-
query.sort! unless namespace.to_s.include?("[]")
-
query.join("&")
-
end
-
-
23
alias_method :to_param, :to_query
-
end
-
# frozen_string_literal: true
-
-
24
require "delegate"
-
-
24
module ActiveSupport
-
24
module Tryable #:nodoc:
-
24
def try(method_name = nil, *args, &b)
-
if method_name.nil? && block_given?
-
if b.arity == 0
-
instance_eval(&b)
-
else
-
yield self
-
end
-
elsif respond_to?(method_name)
-
public_send(method_name, *args, &b)
-
end
-
end
-
24
ruby2_keywords(:try) if respond_to?(:ruby2_keywords, true)
-
-
24
def try!(method_name = nil, *args, &b)
-
if method_name.nil? && block_given?
-
if b.arity == 0
-
instance_eval(&b)
-
else
-
yield self
-
end
-
else
-
public_send(method_name, *args, &b)
-
end
-
end
-
24
ruby2_keywords(:try!) if respond_to?(:ruby2_keywords, true)
-
end
-
end
-
-
24
class Object
-
24
include ActiveSupport::Tryable
-
-
##
-
# :method: try
-
#
-
# :call-seq:
-
# try(*a, &b)
-
#
-
# Invokes the public method whose name goes as first argument just like
-
# +public_send+ does, except that if the receiver does not respond to it the
-
# call returns +nil+ rather than raising an exception.
-
#
-
# This method is defined to be able to write
-
#
-
# @person.try(:name)
-
#
-
# instead of
-
#
-
# @person.name if @person
-
#
-
# +try+ calls can be chained:
-
#
-
# @person.try(:spouse).try(:name)
-
#
-
# instead of
-
#
-
# @person.spouse.name if @person && @person.spouse
-
#
-
# +try+ will also return +nil+ if the receiver does not respond to the method:
-
#
-
# @person.try(:non_existing_method) # => nil
-
#
-
# instead of
-
#
-
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
-
#
-
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
-
# to the method:
-
#
-
# nil.try(:to_i) # => nil, rather than 0
-
#
-
# Arguments and blocks are forwarded to the method if invoked:
-
#
-
# @posts.try(:each_slice, 2) do |a, b|
-
# ...
-
# end
-
#
-
# The number of arguments in the signature must match. If the object responds
-
# to the method the call is attempted and +ArgumentError+ is still raised
-
# in case of argument mismatch.
-
#
-
# If +try+ is called without arguments it yields the receiver to a given
-
# block unless it is +nil+:
-
#
-
# @person.try do |p|
-
# ...
-
# end
-
#
-
# You can also call try with a block without accepting an argument, and the block
-
# will be instance_eval'ed instead:
-
#
-
# @person.try { upcase.truncate(50) }
-
#
-
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
-
# with instances of classes that do not have +Object+ among their ancestors,
-
# like direct subclasses of +BasicObject+.
-
-
##
-
# :method: try!
-
#
-
# :call-seq:
-
# try!(*a, &b)
-
#
-
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
-
# not +nil+ and does not implement the tried method.
-
#
-
# "a".try!(:upcase) # => "A"
-
# nil.try!(:upcase) # => nil
-
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
-
end
-
-
24
class Delegator
-
24
include ActiveSupport::Tryable
-
-
##
-
# :method: try
-
#
-
# :call-seq:
-
# try(a*, &b)
-
#
-
# See Object#try
-
-
##
-
# :method: try!
-
#
-
# :call-seq:
-
# try!(a*, &b)
-
#
-
# See Object#try!
-
end
-
-
24
class NilClass
-
# Calling +try+ on +nil+ always returns +nil+.
-
# It becomes especially helpful when navigating through associations that may return +nil+.
-
#
-
# nil.try(:name) # => nil
-
#
-
# Without +try+
-
# @person && @person.children.any? && @person.children.first.name
-
#
-
# With +try+
-
# @person.try(:children).try(:first).try(:name)
-
24
def try(_method_name = nil, *)
-
nil
-
end
-
-
# Calling +try!+ on +nil+ always returns +nil+.
-
#
-
# nil.try!(:name) # => nil
-
24
def try!(_method_name = nil, *)
-
nil
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/option_merger"
-
-
2
class Object
-
# An elegant way to factor duplication out of options passed to a series of
-
# method calls. Each method called in the block, with the block variable as
-
# the receiver, will have its options merged with the default +options+ hash
-
# provided. Each method called on the block variable must take an options
-
# hash as its final argument.
-
#
-
# Without <tt>with_options</tt>, this code contains duplication:
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :customers, dependent: :destroy
-
# has_many :products, dependent: :destroy
-
# has_many :invoices, dependent: :destroy
-
# has_many :expenses, dependent: :destroy
-
# end
-
#
-
# Using <tt>with_options</tt>, we can remove the duplication:
-
#
-
# class Account < ActiveRecord::Base
-
# with_options dependent: :destroy do |assoc|
-
# assoc.has_many :customers
-
# assoc.has_many :products
-
# assoc.has_many :invoices
-
# assoc.has_many :expenses
-
# end
-
# end
-
#
-
# It can also be used with an explicit receiver:
-
#
-
# I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n|
-
# subject i18n.t :subject
-
# body i18n.t :body, user_name: user.name
-
# end
-
#
-
# When you don't pass an explicit receiver, it executes the whole block
-
# in merging options context:
-
#
-
# class Account < ActiveRecord::Base
-
# with_options dependent: :destroy do
-
# has_many :customers
-
# has_many :products
-
# has_many :invoices
-
# has_many :expenses
-
# end
-
# end
-
#
-
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
-
#
-
# NOTE: Each nesting level will merge inherited defaults in addition to their own.
-
#
-
# class Post < ActiveRecord::Base
-
# with_options if: :persisted?, length: { minimum: 50 } do
-
# validates :content, if: -> { content.present? }
-
# end
-
# end
-
#
-
# The code is equivalent to:
-
#
-
# validates :content, length: { minimum: 50 }, if: -> { content.present? }
-
#
-
# Hence the inherited default for +if+ key is ignored.
-
#
-
# NOTE: You cannot call class methods implicitly inside of with_options.
-
# You can access these methods using the class name instead:
-
#
-
# class Phone < ActiveRecord::Base
-
# enum phone_number_type: { home: 0, office: 1, mobile: 2 }
-
#
-
# with_options presence: true do
-
# validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
-
# end
-
# end
-
#
-
2
def with_options(options, &block)
-
option_merger = ActiveSupport::OptionMerger.new(self, options)
-
block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/range/conversions"
-
1
require "active_support/core_ext/range/compare_range"
-
1
require "active_support/core_ext/range/include_time_with_zone"
-
1
require "active_support/core_ext/range/overlaps"
-
1
require "active_support/core_ext/range/each"
-
# frozen_string_literal: true
-
-
1
module ActiveSupport
-
1
module CompareWithRange
-
# Extends the default Range#=== to support range comparisons.
-
# (1..5) === (1..5) # => true
-
# (1..5) === (2..3) # => true
-
# (1..5) === (1...6) # => true
-
# (1..5) === (2..6) # => false
-
#
-
# The native Range#=== behavior is untouched.
-
# ('a'..'f') === ('c') # => true
-
# (5..9) === (11) # => false
-
#
-
# The given range must be fully bounded, with both start and end.
-
1
def ===(value)
-
if value.is_a?(::Range)
-
is_backwards_op = value.exclude_end? ? :>= : :>
-
return false if value.begin && value.end && value.begin.send(is_backwards_op, value.end)
-
# 1...10 includes 1..9 but it does not include 1..10.
-
# 1..10 includes 1...11 but it does not include 1...12.
-
operator = exclude_end? && !value.exclude_end? ? :< : :<=
-
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
-
super(value.first) && (self.end.nil? || value_max.send(operator, last))
-
else
-
super
-
end
-
end
-
-
# Extends the default Range#include? to support range comparisons.
-
# (1..5).include?(1..5) # => true
-
# (1..5).include?(2..3) # => true
-
# (1..5).include?(1...6) # => true
-
# (1..5).include?(2..6) # => false
-
#
-
# The native Range#include? behavior is untouched.
-
# ('a'..'f').include?('c') # => true
-
# (5..9).include?(11) # => false
-
#
-
# The given range must be fully bounded, with both start and end.
-
1
def include?(value)
-
if value.is_a?(::Range)
-
is_backwards_op = value.exclude_end? ? :>= : :>
-
return false if value.begin && value.end && value.begin.send(is_backwards_op, value.end)
-
# 1...10 includes 1..9 but it does not include 1..10.
-
# 1..10 includes 1...11 but it does not include 1...12.
-
operator = exclude_end? && !value.exclude_end? ? :< : :<=
-
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
-
super(value.first) && (self.end.nil? || value_max.send(operator, last))
-
else
-
super
-
end
-
end
-
-
# Extends the default Range#cover? to support range comparisons.
-
# (1..5).cover?(1..5) # => true
-
# (1..5).cover?(2..3) # => true
-
# (1..5).cover?(1...6) # => true
-
# (1..5).cover?(2..6) # => false
-
#
-
# The native Range#cover? behavior is untouched.
-
# ('a'..'f').cover?('c') # => true
-
# (5..9).cover?(11) # => false
-
#
-
# The given range must be fully bounded, with both start and end.
-
1
def cover?(value)
-
if value.is_a?(::Range)
-
is_backwards_op = value.exclude_end? ? :>= : :>
-
return false if value.begin && value.end && value.begin.send(is_backwards_op, value.end)
-
# 1...10 covers 1..9 but it does not cover 1..10.
-
# 1..10 covers 1...11 but it does not cover 1...12.
-
operator = exclude_end? && !value.exclude_end? ? :< : :<=
-
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
-
super(value.first) && (self.end.nil? || value_max.send(operator, last))
-
else
-
super
-
end
-
end
-
end
-
end
-
-
1
Range.prepend(ActiveSupport::CompareWithRange)
-
# frozen_string_literal: true
-
-
1
module ActiveSupport
-
1
module RangeWithFormat
-
1
RANGE_FORMATS = {
-
db: -> (start, stop) do
-
case start
-
when String then "BETWEEN '#{start}' AND '#{stop}'"
-
else
-
"BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
-
end
-
end
-
}
-
-
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
-
#
-
# range = (1..100) # => 1..100
-
#
-
# range.to_s # => "1..100"
-
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
-
#
-
# == Adding your own range formats to to_s
-
# You can add your own formats to the Range::RANGE_FORMATS hash.
-
# Use the format name as the hash key and a Proc instance.
-
#
-
# # config/initializers/range_formats.rb
-
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
-
1
def to_s(format = :default)
-
if formatter = RANGE_FORMATS[format]
-
formatter.call(first, last)
-
else
-
super()
-
end
-
end
-
-
1
alias_method :to_default_s, :to_s
-
1
alias_method :to_formatted_s, :to_s
-
end
-
end
-
-
1
Range.prepend(ActiveSupport::RangeWithFormat)
-
# frozen_string_literal: true
-
-
1
require "active_support/time_with_zone"
-
-
1
module ActiveSupport
-
1
module EachTimeWithZone #:nodoc:
-
1
def each(&block)
-
5
ensure_iteration_allowed
-
5
super
-
end
-
-
1
def step(n = 1, &block)
-
ensure_iteration_allowed
-
super
-
end
-
-
1
private
-
1
def ensure_iteration_allowed
-
5
raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone)
-
end
-
end
-
end
-
-
1
Range.prepend(ActiveSupport::EachTimeWithZone)
-
# frozen_string_literal: true
-
-
require "active_support/deprecation"
-
-
ActiveSupport::Deprecation.warn "You have required `active_support/core_ext/range/include_range`. " \
-
"This file will be removed in Rails 6.1. You should require `active_support/core_ext/range/compare_range` " \
-
"instead."
-
-
require "active_support/core_ext/range/compare_range"
-
# frozen_string_literal: true
-
-
1
require "active_support/time_with_zone"
-
1
require "active_support/deprecation"
-
-
1
module ActiveSupport
-
1
module IncludeTimeWithZone #:nodoc:
-
# Extends the default Range#include? to support ActiveSupport::TimeWithZone.
-
#
-
# (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
-
#
-
1
def include?(value)
-
if self.begin.is_a?(TimeWithZone) || self.end.is_a?(TimeWithZone)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Using `Range#include?` to check the inclusion of a value in
-
a date time range is deprecated.
-
It is recommended to use `Range#cover?` instead of `Range#include?` to
-
check the inclusion of a value in a date time range.
-
MSG
-
cover?(value)
-
else
-
super
-
end
-
end
-
end
-
end
-
-
1
Range.prepend(ActiveSupport::IncludeTimeWithZone)
-
# frozen_string_literal: true
-
-
1
class Range
-
# Compare two ranges and see if they overlap each other
-
# (1..5).overlaps?(4..6) # => true
-
# (1..5).overlaps?(7..9) # => false
-
1
def overlaps?(other)
-
cover?(other.first) || other.cover?(first)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class Regexp #:nodoc:
-
1
def multiline?
-
options & MULTILINE == MULTILINE
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "securerandom"
-
-
1
module SecureRandom
-
1
BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
-
1
BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
-
-
# SecureRandom.base58 generates a random base58 string.
-
#
-
# The argument _n_ specifies the length of the random string to be generated.
-
#
-
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
-
#
-
# The result may contain alphanumeric characters except 0, O, I and l.
-
#
-
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
-
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
-
1
def self.base58(n = 16)
-
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
-
idx = byte % 64
-
idx = SecureRandom.random_number(58) if idx >= 58
-
BASE58_ALPHABET[idx]
-
end.join
-
end
-
-
# SecureRandom.base36 generates a random base36 string in lowercase.
-
#
-
# The argument _n_ specifies the length of the random string to be generated.
-
#
-
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
-
# This method can be used over +base58+ if a deterministic case key is necessary.
-
#
-
# The result will contain alphanumeric characters in lowercase.
-
#
-
# p SecureRandom.base36 # => "4kugl2pdqmscqtje"
-
# p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
-
1
def self.base36(n = 16)
-
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
-
idx = byte % 64
-
idx = SecureRandom.random_number(36) if idx >= 36
-
BASE36_ALPHABET[idx]
-
end.join
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/string/conversions"
-
1
require "active_support/core_ext/string/filters"
-
1
require "active_support/core_ext/string/multibyte"
-
1
require "active_support/core_ext/string/starts_ends_with"
-
1
require "active_support/core_ext/string/inflections"
-
1
require "active_support/core_ext/string/access"
-
1
require "active_support/core_ext/string/behavior"
-
1
require "active_support/core_ext/string/output_safety"
-
1
require "active_support/core_ext/string/exclude"
-
1
require "active_support/core_ext/string/strip"
-
1
require "active_support/core_ext/string/inquiry"
-
1
require "active_support/core_ext/string/indent"
-
1
require "active_support/core_ext/string/zones"
-
# frozen_string_literal: true
-
-
1
class String
-
# If you pass a single integer, returns a substring of one character at that
-
# position. The first character of the string is at position 0, the next at
-
# position 1, and so on. If a range is supplied, a substring containing
-
# characters at offsets given by the range is returned. In both cases, if an
-
# offset is negative, it is counted from the end of the string. Returns +nil+
-
# if the initial offset falls outside the string. Returns an empty string if
-
# the beginning of the range is greater than the end of the string.
-
#
-
# str = "hello"
-
# str.at(0) # => "h"
-
# str.at(1..3) # => "ell"
-
# str.at(-2) # => "l"
-
# str.at(-2..-1) # => "lo"
-
# str.at(5) # => nil
-
# str.at(5..-1) # => ""
-
#
-
# If a Regexp is given, the matching portion of the string is returned.
-
# If a String is given, that given string is returned if it occurs in
-
# the string. In both cases, +nil+ is returned if there is no match.
-
#
-
# str = "hello"
-
# str.at(/lo/) # => "lo"
-
# str.at(/ol/) # => nil
-
# str.at("lo") # => "lo"
-
# str.at("ol") # => nil
-
1
def at(position)
-
self[position]
-
end
-
-
# Returns a substring from the given position to the end of the string.
-
# If the position is negative, it is counted from the end of the string.
-
#
-
# str = "hello"
-
# str.from(0) # => "hello"
-
# str.from(3) # => "lo"
-
# str.from(-2) # => "lo"
-
#
-
# You can mix it with +to+ method and do fun things like:
-
#
-
# str = "hello"
-
# str.from(0).to(-1) # => "hello"
-
# str.from(1).to(-2) # => "ell"
-
1
def from(position)
-
self[position, length]
-
end
-
-
# Returns a substring from the beginning of the string to the given position.
-
# If the position is negative, it is counted from the end of the string.
-
#
-
# str = "hello"
-
# str.to(0) # => "h"
-
# str.to(3) # => "hell"
-
# str.to(-2) # => "hell"
-
#
-
# You can mix it with +from+ method and do fun things like:
-
#
-
# str = "hello"
-
# str.from(0).to(-1) # => "hello"
-
# str.from(1).to(-2) # => "ell"
-
1
def to(position)
-
position += size if position < 0
-
self[0, position + 1] || +""
-
end
-
-
# Returns the first character. If a limit is supplied, returns a substring
-
# from the beginning of the string until it reaches the limit value. If the
-
# given limit is greater than or equal to the string length, returns a copy of self.
-
#
-
# str = "hello"
-
# str.first # => "h"
-
# str.first(1) # => "h"
-
# str.first(2) # => "he"
-
# str.first(0) # => ""
-
# str.first(6) # => "hello"
-
1
def first(limit = 1)
-
self[0, limit] || raise(ArgumentError, "negative limit")
-
end
-
-
# Returns the last character of the string. If a limit is supplied, returns a substring
-
# from the end of the string until it reaches the limit value (counting backwards). If
-
# the given limit is greater than or equal to the string length, returns a copy of self.
-
#
-
# str = "hello"
-
# str.last # => "o"
-
# str.last(1) # => "o"
-
# str.last(2) # => "lo"
-
# str.last(0) # => ""
-
# str.last(6) # => "hello"
-
1
def last(limit = 1)
-
self[[length - limit, 0].max, limit] || raise(ArgumentError, "negative limit")
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class String
-
# Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
-
1
def acts_like_string?
-
true
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "date"
-
2
require "active_support/core_ext/time/calculations"
-
-
2
class String
-
# Converts a string to a Time value.
-
# The +form+ can be either :utc or :local (default :local).
-
#
-
# The time is parsed using Time.parse method.
-
# If +form+ is :local, then the time is in the system timezone.
-
# If the date part is missing then the current date is used and if
-
# the time part is missing then it is assumed to be 00:00:00.
-
#
-
# "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100
-
# "06:12".to_time # => 2012-12-13 06:12:00 +0100
-
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
-
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
-
# "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
-
# "12/13/2012".to_time # => ArgumentError: argument out of range
-
2
def to_time(form = :local)
-
parts = Date._parse(self, false)
-
used_keys = %i(year mon mday hour min sec sec_fraction offset)
-
return if (parts.keys & used_keys).empty?
-
-
now = Time.now
-
time = Time.new(
-
parts.fetch(:year, now.year),
-
parts.fetch(:mon, now.month),
-
parts.fetch(:mday, now.day),
-
parts.fetch(:hour, 0),
-
parts.fetch(:min, 0),
-
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
-
parts.fetch(:offset, form == :utc ? 0 : nil)
-
)
-
-
form == :utc ? time.utc : time.to_time
-
end
-
-
# Converts a string to a Date value.
-
#
-
# "1-1-2012".to_date # => Sun, 01 Jan 2012
-
# "01/01/2012".to_date # => Sun, 01 Jan 2012
-
# "2012-12-13".to_date # => Thu, 13 Dec 2012
-
# "12/13/2012".to_date # => ArgumentError: invalid date
-
2
def to_date
-
::Date.parse(self, false) unless blank?
-
end
-
-
# Converts a string to a DateTime value.
-
#
-
# "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000
-
# "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000
-
# "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000
-
# "12/13/2012".to_datetime # => ArgumentError: invalid date
-
2
def to_datetime
-
::DateTime.parse(self, false) unless blank?
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class String
-
# The inverse of <tt>String#include?</tt>. Returns true if the string
-
# does not include the other string.
-
#
-
# "hello".exclude? "lo" # => false
-
# "hello".exclude? "ol" # => true
-
# "hello".exclude? ?h # => false
-
1
def exclude?(string)
-
!include?(string)
-
end
-
end
-
# frozen_string_literal: true
-
-
23
class String
-
# Returns the string, first removing all whitespace on both ends of
-
# the string, and then changing remaining consecutive whitespace
-
# groups into one space each.
-
#
-
# Note that it handles both ASCII and Unicode whitespace.
-
#
-
# %{ Multi-line
-
# string }.squish # => "Multi-line string"
-
# " foo bar \n \t boo".squish # => "foo bar boo"
-
23
def squish
-
dup.squish!
-
end
-
-
# Performs a destructive squish. See String#squish.
-
# str = " foo bar \n \t boo"
-
# str.squish! # => "foo bar boo"
-
# str # => "foo bar boo"
-
23
def squish!
-
gsub!(/[[:space:]]+/, " ")
-
strip!
-
self
-
end
-
-
# Returns a new string with all occurrences of the patterns removed.
-
# str = "foo bar test"
-
# str.remove(" test") # => "foo bar"
-
# str.remove(" test", /bar/) # => "foo "
-
# str # => "foo bar test"
-
23
def remove(*patterns)
-
dup.remove!(*patterns)
-
end
-
-
# Alters the string by removing all occurrences of the patterns.
-
# str = "foo bar test"
-
# str.remove!(" test", /bar/) # => "foo "
-
# str # => "foo "
-
23
def remove!(*patterns)
-
patterns.each do |pattern|
-
gsub! pattern, ""
-
end
-
-
self
-
end
-
-
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
-
#
-
# 'Once upon a time in a world far far away'.truncate(27)
-
# # => "Once upon a time in a wo..."
-
#
-
# Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
-
#
-
# 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
-
# # => "Once upon a time in a..."
-
#
-
# 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
-
# # => "Once upon a time in a..."
-
#
-
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
-
# for a total length not exceeding <tt>length</tt>:
-
#
-
# 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
-
# # => "And they f... (continued)"
-
23
def truncate(truncate_at, options = {})
-
return dup unless length > truncate_at
-
-
omission = options[:omission] || "..."
-
length_with_room_for_omission = truncate_at - omission.length
-
stop = \
-
if options[:separator]
-
rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
-
else
-
length_with_room_for_omission
-
end
-
-
+"#{self[0, stop]}#{omission}"
-
end
-
-
# Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
-
# breaking string encoding by splitting multibyte characters or breaking
-
# grapheme clusters ("perceptual characters") by truncating at combining
-
# characters.
-
#
-
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
-
# => 20
-
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
-
# => 80
-
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
-
# => "🔪🔪🔪🔪…"
-
#
-
# The truncated text ends with the <tt>:omission</tt> string, defaulting
-
# to "…", for a total length not exceeding <tt>bytesize</tt>.
-
23
def truncate_bytes(truncate_at, omission: "…")
-
omission ||= ""
-
-
case
-
when bytesize <= truncate_at
-
dup
-
when omission.bytesize > truncate_at
-
raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes"
-
when omission.bytesize == truncate_at
-
omission.dup
-
else
-
self.class.new.tap do |cut|
-
cut_at = truncate_at - omission.bytesize
-
-
scan(/\X/) do |grapheme|
-
if cut.bytesize + grapheme.bytesize <= cut_at
-
cut << grapheme
-
else
-
break
-
end
-
end
-
-
cut << omission
-
end
-
end
-
end
-
-
# Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
-
#
-
# 'Once upon a time in a world far far away'.truncate_words(4)
-
# # => "Once upon a time..."
-
#
-
# Pass a string or regexp <tt>:separator</tt> to specify a different separator of words:
-
#
-
# 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>')
-
# # => "Once<br>upon<br>a<br>time<br>in..."
-
#
-
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."):
-
#
-
# 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
-
# # => "And they found that many... (continued)"
-
23
def truncate_words(words_count, options = {})
-
sep = options[:separator] || /\s+/
-
sep = Regexp.escape(sep.to_s) unless Regexp === sep
-
if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
-
$1 + (options[:omission] || "...")
-
else
-
dup
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class String
-
# Same as +indent+, except it indents the receiver in-place.
-
#
-
# Returns the indented string, or +nil+ if there was nothing to indent.
-
1
def indent!(amount, indent_string = nil, indent_empty_lines = false)
-
indent_string = indent_string || self[/^[ \t]/] || " "
-
re = indent_empty_lines ? /^/ : /^(?!$)/
-
gsub!(re, indent_string * amount)
-
end
-
-
# Indents the lines in the receiver:
-
#
-
# <<EOS.indent(2)
-
# def some_method
-
# some_code
-
# end
-
# EOS
-
# # =>
-
# def some_method
-
# some_code
-
# end
-
#
-
# The second argument, +indent_string+, specifies which indent string to
-
# use. The default is +nil+, which tells the method to make a guess by
-
# peeking at the first indented line, and fallback to a space if there is
-
# none.
-
#
-
# " foo".indent(2) # => " foo"
-
# "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
-
# "foo".indent(2, "\t") # => "\t\tfoo"
-
#
-
# While +indent_string+ is typically one space or tab, it may be any string.
-
#
-
# The third argument, +indent_empty_lines+, is a flag that says whether
-
# empty lines should be indented. Default is false.
-
#
-
# "foo\n\nbar".indent(2) # => " foo\n\n bar"
-
# "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
-
#
-
1
def indent(amount, indent_string = nil, indent_empty_lines = false)
-
dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) }
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/inflector/methods"
-
23
require "active_support/inflector/transliterate"
-
-
# String inflections define new methods on the String class to transform names for different purposes.
-
# For instance, you can figure out the name of a table from the name of a class.
-
#
-
# 'ScaleScore'.tableize # => "scale_scores"
-
#
-
23
class String
-
# Returns the plural form of the word in the string.
-
#
-
# If the optional parameter +count+ is specified,
-
# the singular form will be returned if <tt>count == 1</tt>.
-
# For any other value of +count+ the plural will be returned.
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be pluralized as a word of that language.
-
# By default, this parameter is set to <tt>:en</tt>.
-
# You must define your own inflection rules for languages other than English.
-
#
-
# 'post'.pluralize # => "posts"
-
# 'octopus'.pluralize # => "octopi"
-
# 'sheep'.pluralize # => "sheep"
-
# 'words'.pluralize # => "words"
-
# 'the blue mailman'.pluralize # => "the blue mailmen"
-
# 'CamelOctopus'.pluralize # => "CamelOctopi"
-
# 'apple'.pluralize(1) # => "apple"
-
# 'apple'.pluralize(2) # => "apples"
-
# 'ley'.pluralize(:es) # => "leyes"
-
# 'ley'.pluralize(1, :es) # => "ley"
-
#
-
# See ActiveSupport::Inflector.pluralize.
-
23
def pluralize(count = nil, locale = :en)
-
locale = count if count.is_a?(Symbol)
-
if count == 1
-
dup
-
else
-
ActiveSupport::Inflector.pluralize(self, locale)
-
end
-
end
-
-
# The reverse of +pluralize+, returns the singular form of a word in a string.
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be singularized as a word of that language.
-
# By default, this parameter is set to <tt>:en</tt>.
-
# You must define your own inflection rules for languages other than English.
-
#
-
# 'posts'.singularize # => "post"
-
# 'octopi'.singularize # => "octopus"
-
# 'sheep'.singularize # => "sheep"
-
# 'word'.singularize # => "word"
-
# 'the blue mailmen'.singularize # => "the blue mailman"
-
# 'CamelOctopi'.singularize # => "CamelOctopus"
-
# 'leyes'.singularize(:es) # => "ley"
-
#
-
# See ActiveSupport::Inflector.singularize.
-
23
def singularize(locale = :en)
-
ActiveSupport::Inflector.singularize(self, locale)
-
end
-
-
# +constantize+ tries to find a declared constant with the name specified
-
# in the string. It raises a NameError when the name is not in CamelCase
-
# or is not initialized.
-
#
-
# 'Module'.constantize # => Module
-
# 'Class'.constantize # => Class
-
# 'blargle'.constantize # => NameError: wrong constant name blargle
-
#
-
# See ActiveSupport::Inflector.constantize.
-
23
def constantize
-
ActiveSupport::Inflector.constantize(self)
-
end
-
-
# +safe_constantize+ tries to find a declared constant with the name specified
-
# in the string. It returns +nil+ when the name is not in CamelCase
-
# or is not initialized.
-
#
-
# 'Module'.safe_constantize # => Module
-
# 'Class'.safe_constantize # => Class
-
# 'blargle'.safe_constantize # => nil
-
#
-
# See ActiveSupport::Inflector.safe_constantize.
-
23
def safe_constantize
-
ActiveSupport::Inflector.safe_constantize(self)
-
end
-
-
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
-
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
-
#
-
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
-
#
-
# 'active_record'.camelize # => "ActiveRecord"
-
# 'active_record'.camelize(:lower) # => "activeRecord"
-
# 'active_record/errors'.camelize # => "ActiveRecord::Errors"
-
# 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
-
#
-
# +camelize+ is also aliased as +camelcase+.
-
#
-
# See ActiveSupport::Inflector.camelize.
-
23
def camelize(first_letter = :upper)
-
3
case first_letter
-
when :upper
-
3
ActiveSupport::Inflector.camelize(self, true)
-
when :lower
-
ActiveSupport::Inflector.camelize(self, false)
-
else
-
raise ArgumentError, "Invalid option, use either :upper or :lower."
-
end
-
end
-
23
alias_method :camelcase, :camelize
-
-
# Capitalizes all the words and replaces some characters in the string to create
-
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
-
# used in the Rails internals.
-
#
-
# The trailing '_id','Id'.. can be kept and capitalized by setting the
-
# optional parameter +keep_id_suffix+ to true.
-
# By default, this parameter is false.
-
#
-
# 'man from the boondocks'.titleize # => "Man From The Boondocks"
-
# 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
-
# 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
-
#
-
# +titleize+ is also aliased as +titlecase+.
-
#
-
# See ActiveSupport::Inflector.titleize.
-
23
def titleize(keep_id_suffix: false)
-
ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
-
end
-
23
alias_method :titlecase, :titleize
-
-
# The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
-
#
-
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
-
#
-
# 'ActiveModel'.underscore # => "active_model"
-
# 'ActiveModel::Errors'.underscore # => "active_model/errors"
-
#
-
# See ActiveSupport::Inflector.underscore.
-
23
def underscore
-
730
ActiveSupport::Inflector.underscore(self)
-
end
-
-
# Replaces underscores with dashes in the string.
-
#
-
# 'puni_puni'.dasherize # => "puni-puni"
-
#
-
# See ActiveSupport::Inflector.dasherize.
-
23
def dasherize
-
ActiveSupport::Inflector.dasherize(self)
-
end
-
-
# Removes the module part from the constant expression in the string.
-
#
-
# 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections"
-
# 'Inflections'.demodulize # => "Inflections"
-
# '::Inflections'.demodulize # => "Inflections"
-
# ''.demodulize # => ''
-
#
-
# See ActiveSupport::Inflector.demodulize.
-
#
-
# See also +deconstantize+.
-
23
def demodulize
-
ActiveSupport::Inflector.demodulize(self)
-
end
-
-
# Removes the rightmost segment from the constant expression in the string.
-
#
-
# 'Net::HTTP'.deconstantize # => "Net"
-
# '::Net::HTTP'.deconstantize # => "::Net"
-
# 'String'.deconstantize # => ""
-
# '::String'.deconstantize # => ""
-
# ''.deconstantize # => ""
-
#
-
# See ActiveSupport::Inflector.deconstantize.
-
#
-
# See also +demodulize+.
-
23
def deconstantize
-
ActiveSupport::Inflector.deconstantize(self)
-
end
-
-
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be parameterized as a word of that language.
-
# By default, this parameter is set to <tt>nil</tt> and it will use
-
# the configured <tt>I18n.locale</tt>.
-
#
-
# class Person
-
# def to_param
-
# "#{id}-#{name.parameterize}"
-
# end
-
# end
-
#
-
# @person = Person.find(1)
-
# # => #<Person id: 1, name: "Donald E. Knuth">
-
#
-
# <%= link_to(@person.name, person_path) %>
-
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
-
#
-
# To preserve the case of the characters in a string, use the +preserve_case+ argument.
-
#
-
# class Person
-
# def to_param
-
# "#{id}-#{name.parameterize(preserve_case: true)}"
-
# end
-
# end
-
#
-
# @person = Person.find(1)
-
# # => #<Person id: 1, name: "Donald E. Knuth">
-
#
-
# <%= link_to(@person.name, person_path) %>
-
# # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
-
#
-
# See ActiveSupport::Inflector.parameterize.
-
23
def parameterize(separator: "-", preserve_case: false, locale: nil)
-
ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
-
end
-
-
# Creates the name of a table like Rails does for models to table names. This method
-
# uses the +pluralize+ method on the last word in the string.
-
#
-
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
-
# 'ham_and_egg'.tableize # => "ham_and_eggs"
-
# 'fancyCategory'.tableize # => "fancy_categories"
-
#
-
# See ActiveSupport::Inflector.tableize.
-
23
def tableize
-
ActiveSupport::Inflector.tableize(self)
-
end
-
-
# Creates a class name from a plural table name like Rails does for table names to models.
-
# Note that this returns a string and not a class. (To convert to an actual class
-
# follow +classify+ with +constantize+.)
-
#
-
# 'ham_and_eggs'.classify # => "HamAndEgg"
-
# 'posts'.classify # => "Post"
-
#
-
# See ActiveSupport::Inflector.classify.
-
23
def classify
-
ActiveSupport::Inflector.classify(self)
-
end
-
-
# Capitalizes the first word, turns underscores into spaces, and (by default)strips a
-
# trailing '_id' if present.
-
# Like +titleize+, this is meant for creating pretty output.
-
#
-
# The capitalization of the first word can be turned off by setting the
-
# optional parameter +capitalize+ to false.
-
# By default, this parameter is true.
-
#
-
# The trailing '_id' can be kept and capitalized by setting the
-
# optional parameter +keep_id_suffix+ to true.
-
# By default, this parameter is false.
-
#
-
# 'employee_salary'.humanize # => "Employee salary"
-
# 'author_id'.humanize # => "Author"
-
# 'author_id'.humanize(capitalize: false) # => "author"
-
# '_id'.humanize # => "Id"
-
# 'author_id'.humanize(keep_id_suffix: true) # => "Author Id"
-
#
-
# See ActiveSupport::Inflector.humanize.
-
23
def humanize(capitalize: true, keep_id_suffix: false)
-
ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
-
end
-
-
# Converts just the first character to uppercase.
-
#
-
# 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
-
# 'w'.upcase_first # => "W"
-
# ''.upcase_first # => ""
-
#
-
# See ActiveSupport::Inflector.upcase_first.
-
23
def upcase_first
-
ActiveSupport::Inflector.upcase_first(self)
-
end
-
-
# Creates a foreign key name from a class name.
-
# +separate_class_name_and_id_with_underscore+ sets whether
-
# the method should put '_' between the name and 'id'.
-
#
-
# 'Message'.foreign_key # => "message_id"
-
# 'Message'.foreign_key(false) # => "messageid"
-
# 'Admin::Post'.foreign_key # => "post_id"
-
#
-
# See ActiveSupport::Inflector.foreign_key.
-
23
def foreign_key(separate_class_name_and_id_with_underscore = true)
-
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/string_inquirer"
-
1
require "active_support/environment_inquirer"
-
-
1
class String
-
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
-
# which gives you a prettier way to test for equality.
-
#
-
# env = 'production'.inquiry
-
# env.production? # => true
-
# env.development? # => false
-
1
def inquiry
-
ActiveSupport::StringInquirer.new(self)
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/multibyte"
-
-
23
class String
-
# == Multibyte proxy
-
#
-
# +mb_chars+ is a multibyte safe proxy for string methods.
-
#
-
# It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
-
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
-
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
-
#
-
# >> "lj".mb_chars.upcase.to_s
-
# => "LJ"
-
#
-
# NOTE: Ruby 2.4 and later support native Unicode case mappings:
-
#
-
# >> "lj".upcase
-
# => "LJ"
-
#
-
# == Method chaining
-
#
-
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
-
# method chaining on the result of any of these methods.
-
#
-
# name.mb_chars.reverse.length # => 12
-
#
-
# == Interoperability and configuration
-
#
-
# The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
-
# String and Char work like expected. The bang! methods change the internal string representation in the Chars
-
# object. Interoperability problems can be resolved easily with a +to_s+ call.
-
#
-
# For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
-
# information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
-
23
def mb_chars
-
ActiveSupport::Multibyte.proxy_class.new(self)
-
end
-
-
# Returns +true+ if string has utf_8 encoding.
-
#
-
# utf_8_str = "some string".encode "UTF-8"
-
# iso_str = "some string".encode "ISO-8859-1"
-
#
-
# utf_8_str.is_utf8? # => true
-
# iso_str.is_utf8? # => false
-
23
def is_utf8?
-
case encoding
-
when Encoding::UTF_8, Encoding::US_ASCII
-
valid_encoding?
-
when Encoding::ASCII_8BIT
-
dup.force_encoding(Encoding::UTF_8).valid_encoding?
-
else
-
false
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "erb"
-
1
require "active_support/core_ext/module/redefine_method"
-
1
require "active_support/multibyte/unicode"
-
-
1
class ERB
-
1
module Util
-
1
HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
-
1
JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
-
1
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
-
1
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
-
-
# A utility method for escaping HTML tag characters.
-
# This method is also aliased as <tt>h</tt>.
-
#
-
# puts html_escape('is a > 0 & a < 10?')
-
# # => is a > 0 & a < 10?
-
1
def html_escape(s)
-
unwrapped_html_escape(s).html_safe
-
end
-
-
1
silence_redefinition_of_method :h
-
1
alias h html_escape
-
-
1
module_function :h
-
-
1
singleton_class.silence_redefinition_of_method :html_escape
-
1
module_function :html_escape
-
-
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
-
# This method is not for public consumption! Seriously!
-
1
def unwrapped_html_escape(s) # :nodoc:
-
s = s.to_s
-
if s.html_safe?
-
s
-
else
-
CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
-
end
-
end
-
1
module_function :unwrapped_html_escape
-
-
# A utility method for escaping HTML without affecting existing escaped entities.
-
#
-
# html_escape_once('1 < 2 & 3')
-
# # => "1 < 2 & 3"
-
#
-
# html_escape_once('<< Accept & Checkout')
-
# # => "<< Accept & Checkout"
-
1
def html_escape_once(s)
-
result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
-
s.html_safe? ? result.html_safe : result
-
end
-
-
1
module_function :html_escape_once
-
-
# A utility method for escaping HTML entities in JSON strings. Specifically, the
-
# &, > and < characters are replaced with their equivalent unicode escaped form -
-
# \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
-
# escaped as they are treated as newline characters in some JavaScript engines.
-
# These sequences have identical meaning as the original characters inside the
-
# context of a JSON string, so assuming the input is a valid and well-formed
-
# JSON value, the output will have equivalent meaning when parsed:
-
#
-
# json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
-
# # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
-
#
-
# json_escape(json)
-
# # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
-
#
-
# JSON.parse(json) == JSON.parse(json_escape(json))
-
# # => true
-
#
-
# The intended use case for this method is to escape JSON strings before including
-
# them inside a script tag to avoid XSS vulnerability:
-
#
-
# <script>
-
# var currentUser = <%= raw json_escape(current_user.to_json) %>;
-
# </script>
-
#
-
# It is necessary to +raw+ the result of +json_escape+, so that quotation marks
-
# don't get converted to <tt>"</tt> entities. +json_escape+ doesn't
-
# automatically flag the result as HTML safe, since the raw value is unsafe to
-
# use inside HTML attributes.
-
#
-
# If your JSON is being used downstream for insertion into the DOM, be aware of
-
# whether or not it is being inserted via +html()+. Most jQuery plugins do this.
-
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
-
# content returned by your JSON.
-
#
-
# If you need to output JSON elsewhere in your HTML, you can just do something
-
# like this, as any unsafe characters (including quotation marks) will be
-
# automatically escaped for you:
-
#
-
# <div data-user-info="<%= current_user.to_json %>">...</div>
-
#
-
# WARNING: this helper only works with valid JSON. Using this on non-JSON values
-
# will open up serious XSS vulnerabilities. For example, if you replace the
-
# +current_user.to_json+ in the example above with user input instead, the browser
-
# will happily eval() that string as JavaScript.
-
#
-
# The escaping performed in this method is identical to those performed in the
-
# Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
-
# set to true. Because this transformation is idempotent, this helper can be
-
# applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
-
#
-
# Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
-
# is enabled, or if you are unsure where your JSON string originated from, it
-
# is recommended that you always apply this helper (other libraries, such as the
-
# JSON gem, do not provide this kind of protection by default; also some gems
-
# might override +to_json+ to bypass Active Support's encoder).
-
1
def json_escape(s)
-
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
-
s.html_safe? ? result.html_safe : result
-
end
-
-
1
module_function :json_escape
-
end
-
end
-
-
1
class Object
-
1
def html_safe?
-
false
-
end
-
end
-
-
1
class Numeric
-
1
def html_safe?
-
true
-
end
-
end
-
-
1
module ActiveSupport #:nodoc:
-
1
class SafeBuffer < String
-
1
UNSAFE_STRING_METHODS = %w(
-
capitalize chomp chop delete delete_prefix delete_suffix
-
downcase lstrip next reverse rstrip slice squeeze strip
-
succ swapcase tr tr_s unicode_normalize upcase
-
)
-
-
1
UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
-
-
1
alias_method :original_concat, :concat
-
1
private :original_concat
-
-
# Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
-
1
class SafeConcatError < StandardError
-
1
def initialize
-
super "Could not concatenate to the buffer because it is not html safe."
-
end
-
end
-
-
1
def [](*args)
-
if html_safe?
-
new_safe_buffer = super
-
-
if new_safe_buffer
-
new_safe_buffer.instance_variable_set :@html_safe, true
-
end
-
-
new_safe_buffer
-
else
-
to_str[*args]
-
end
-
end
-
-
1
def safe_concat(value)
-
raise SafeConcatError unless html_safe?
-
original_concat(value)
-
end
-
-
1
def initialize(str = "")
-
1
@html_safe = true
-
1
super
-
end
-
-
1
def initialize_copy(other)
-
super
-
@html_safe = other.html_safe?
-
end
-
-
1
def clone_empty
-
self[0, 0]
-
end
-
-
1
def concat(value)
-
super(html_escape_interpolated_argument(value))
-
end
-
1
alias << concat
-
-
1
def insert(index, value)
-
super(index, html_escape_interpolated_argument(value))
-
end
-
-
1
def prepend(value)
-
super(html_escape_interpolated_argument(value))
-
end
-
-
1
def replace(value)
-
super(html_escape_interpolated_argument(value))
-
end
-
-
1
def []=(*args)
-
if args.length == 3
-
super(args[0], args[1], html_escape_interpolated_argument(args[2]))
-
else
-
super(args[0], html_escape_interpolated_argument(args[1]))
-
end
-
end
-
-
1
def +(other)
-
dup.concat(other)
-
end
-
-
1
def *(*)
-
new_safe_buffer = super
-
new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
-
new_safe_buffer
-
end
-
-
1
def %(args)
-
case args
-
when Hash
-
escaped_args = args.transform_values { |arg| html_escape_interpolated_argument(arg) }
-
else
-
escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
-
end
-
-
self.class.new(super(escaped_args))
-
end
-
-
1
def html_safe?
-
defined?(@html_safe) && @html_safe
-
end
-
-
1
def to_s
-
self
-
end
-
-
1
def to_param
-
to_str
-
end
-
-
1
def encode_with(coder)
-
coder.represent_object nil, to_str
-
end
-
-
1
UNSAFE_STRING_METHODS.each do |unsafe_method|
-
20
if unsafe_method.respond_to?(unsafe_method)
-
20
class_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
-
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
-
end # end
-
-
def #{unsafe_method}!(*args) # def capitalize!(*args)
-
@html_safe = false # @html_safe = false
-
super # super
-
end # end
-
EOT
-
end
-
end
-
-
1
UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method|
-
2
if unsafe_method.respond_to?(unsafe_method)
-
2
class_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{unsafe_method}(*args, &block) # def gsub(*args, &block)
-
if block # if block
-
to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params|
-
set_block_back_references(block, $~) # set_block_back_references(block, $~)
-
block.call(*params) # block.call(*params)
-
} # }
-
else # else
-
to_str.#{unsafe_method}(*args) # to_str.gsub(*args)
-
end # end
-
end # end
-
-
def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block)
-
@html_safe = false # @html_safe = false
-
if block # if block
-
super(*args) { |*params| # super(*args) { |*params|
-
set_block_back_references(block, $~) # set_block_back_references(block, $~)
-
block.call(*params) # block.call(*params)
-
} # }
-
else # else
-
super # super
-
end # end
-
end # end
-
EOT
-
end
-
end
-
-
1
private
-
1
def html_escape_interpolated_argument(arg)
-
(!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
-
end
-
-
1
def set_block_back_references(block, match_data)
-
block.binding.eval("proc { |m| $~ = m }").call(match_data)
-
rescue ArgumentError
-
# Can't create binding from C level Proc
-
end
-
end
-
end
-
-
1
class String
-
# Marks a string as trusted safe. It will be inserted into HTML with no
-
# additional escaping performed. It is your responsibility to ensure that the
-
# string contains no malicious content. This method is equivalent to the
-
# +raw+ helper in views. It is recommended that you use +sanitize+ instead of
-
# this method. It should never be called on user input.
-
1
def html_safe
-
ActiveSupport::SafeBuffer.new(self)
-
end
-
end
-
# frozen_string_literal: true
-
-
1
class String
-
1
alias :starts_with? :start_with?
-
1
alias :ends_with? :end_with?
-
end
-
# frozen_string_literal: true
-
-
1
class String
-
# Strips indentation in heredocs.
-
#
-
# For example in
-
#
-
# if options[:usage]
-
# puts <<-USAGE.strip_heredoc
-
# This command does such and such.
-
#
-
# Supported options are:
-
# -h This message
-
# ...
-
# USAGE
-
# end
-
#
-
# the user would see the usage message aligned against the left margin.
-
#
-
# Technically, it looks for the least indented non-empty line
-
# in the whole string, and removes that amount of leading whitespace.
-
1
def strip_heredoc
-
gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "").tap do |stripped|
-
stripped.freeze if frozen?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/string/conversions"
-
2
require "active_support/core_ext/time/zones"
-
-
2
class String
-
# Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
-
# is set, otherwise converts String to a Time via String#to_time
-
2
def in_time_zone(zone = ::Time.zone)
-
if zone
-
::Time.find_zone!(zone).parse(self)
-
else
-
to_time
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/symbol/starts_ends_with"
-
# frozen_string_literal: true
-
-
2
class Symbol
-
def start_with?(*prefixes)
-
152
to_s.start_with?(*prefixes)
-
2
end unless method_defined?(:start_with?)
-
-
def end_with?(*suffixes)
-
to_s.end_with?(*suffixes)
-
2
end unless method_defined?(:end_with?)
-
-
2
alias :starts_with? :start_with?
-
2
alias :ends_with? :end_with?
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/time/acts_like"
-
2
require "active_support/core_ext/time/calculations"
-
2
require "active_support/core_ext/time/compatibility"
-
2
require "active_support/core_ext/time/conversions"
-
2
require "active_support/core_ext/time/zones"
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/object/acts_like"
-
-
23
class Time
-
# Duck-types as a Time-like class. See Object#acts_like?.
-
23
def acts_like_time?
-
true
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/duration"
-
23
require "active_support/core_ext/time/conversions"
-
23
require "active_support/time_with_zone"
-
23
require "active_support/core_ext/time/zones"
-
23
require "active_support/core_ext/date_and_time/calculations"
-
23
require "active_support/core_ext/date/calculations"
-
-
23
class Time
-
23
include DateAndTime::Calculations
-
-
23
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-
-
23
class << self
-
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
-
23
def ===(other)
-
1996
super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))
-
end
-
-
# Returns the number of days in the given month.
-
# If no year is specified, it will use the current year.
-
23
def days_in_month(month, year = current.year)
-
if month == 2 && ::Date.gregorian_leap?(year)
-
29
-
else
-
COMMON_YEAR_DAYS_IN_MONTH[month]
-
end
-
end
-
-
# Returns the number of days in the given year.
-
# If no year is specified, it will use the current year.
-
23
def days_in_year(year = current.year)
-
days_in_month(2, year) + 337
-
end
-
-
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
-
23
def current
-
::Time.zone ? ::Time.zone.now : ::Time.now
-
end
-
-
# Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
-
# instances can be used when called with a single argument
-
23
def at_with_coercion(*args)
-
return at_without_coercion(*args) if args.size != 1
-
-
# Time.at can be called with a time or numerical value
-
time_or_number = args.first
-
-
if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime)
-
at_without_coercion(time_or_number.to_f).getlocal
-
else
-
at_without_coercion(time_or_number)
-
end
-
end
-
23
alias_method :at_without_coercion, :at
-
23
alias_method :at, :at_with_coercion
-
-
# Creates a +Time+ instance from an RFC 3339 string.
-
#
-
# Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
-
#
-
# If the time or offset components are missing then an +ArgumentError+ will be raised.
-
#
-
# Time.rfc3339('1999-12-31') # => ArgumentError: invalid date
-
23
def rfc3339(str)
-
parts = Date._rfc3339(str)
-
-
raise ArgumentError, "invalid date" if parts.empty?
-
-
Time.new(
-
parts.fetch(:year),
-
parts.fetch(:mon),
-
parts.fetch(:mday),
-
parts.fetch(:hour),
-
parts.fetch(:min),
-
parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
-
parts.fetch(:offset)
-
)
-
end
-
end
-
-
# Returns the number of seconds since 00:00:00.
-
#
-
# Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0
-
# Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0
-
# Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0
-
23
def seconds_since_midnight
-
to_i - change(hour: 0).to_i + (usec / 1.0e+6)
-
end
-
-
# Returns the number of seconds until 23:59:59.
-
#
-
# Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
-
# Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
-
# Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
-
23
def seconds_until_end_of_day
-
end_of_day.to_i - to_i
-
end
-
-
# Returns the fraction of a second as a +Rational+
-
#
-
# Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
-
23
def sec_fraction
-
subsec
-
end
-
-
# Returns a new Time where one or more of the elements have been changed according
-
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
-
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
-
# the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
-
# and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter
-
# takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>,
-
# <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>,
-
# <tt>:offset</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
-
#
-
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
-
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
-
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
-
23
def change(options)
-
new_year = options.fetch(:year, year)
-
new_month = options.fetch(:month, month)
-
new_day = options.fetch(:day, day)
-
new_hour = options.fetch(:hour, hour)
-
new_min = options.fetch(:min, options[:hour] ? 0 : min)
-
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
-
new_offset = options.fetch(:offset, nil)
-
-
if new_nsec = options[:nsec]
-
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
-
new_usec = Rational(new_nsec, 1000)
-
else
-
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
-
end
-
-
raise ArgumentError, "argument out of range" if new_usec >= 1000000
-
-
new_sec += Rational(new_usec, 1000000)
-
-
if new_offset
-
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
-
elsif utc?
-
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
-
elsif zone
-
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
-
else
-
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
-
end
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days
-
# according to the proleptic Gregorian calendar. The +options+ parameter
-
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
-
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
-
# <tt>:seconds</tt>.
-
#
-
# Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
-
# Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
-
# Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
-
# Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
-
# Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
-
23
def advance(options)
-
unless options[:weeks].nil?
-
options[:weeks], partial_weeks = options[:weeks].divmod(1)
-
options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
-
end
-
-
unless options[:days].nil?
-
options[:days], partial_days = options[:days].divmod(1)
-
options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
-
end
-
-
d = to_date.gregorian.advance(options)
-
time_advanced_by_date = change(year: d.year, month: d.month, day: d.day)
-
seconds_to_advance = \
-
options.fetch(:seconds, 0) +
-
options.fetch(:minutes, 0) * 60 +
-
options.fetch(:hours, 0) * 3600
-
-
if seconds_to_advance.zero?
-
time_advanced_by_date
-
else
-
time_advanced_by_date.since(seconds_to_advance)
-
end
-
end
-
-
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
-
23
def ago(seconds)
-
since(-seconds)
-
end
-
-
# Returns a new Time representing the time a number of seconds since the instance time
-
23
def since(seconds)
-
self + seconds
-
rescue
-
to_datetime.since(seconds)
-
end
-
23
alias :in :since
-
-
# Returns a new Time representing the start of the day (0:00)
-
23
def beginning_of_day
-
change(hour: 0)
-
end
-
23
alias :midnight :beginning_of_day
-
23
alias :at_midnight :beginning_of_day
-
23
alias :at_beginning_of_day :beginning_of_day
-
-
# Returns a new Time representing the middle of the day (12:00)
-
23
def middle_of_day
-
change(hour: 12)
-
end
-
23
alias :midday :middle_of_day
-
23
alias :noon :middle_of_day
-
23
alias :at_midday :middle_of_day
-
23
alias :at_noon :middle_of_day
-
23
alias :at_middle_of_day :middle_of_day
-
-
# Returns a new Time representing the end of the day, 23:59:59.999999
-
23
def end_of_day
-
change(
-
hour: 23,
-
min: 59,
-
sec: 59,
-
usec: Rational(999999999, 1000)
-
)
-
end
-
23
alias :at_end_of_day :end_of_day
-
-
# Returns a new Time representing the start of the hour (x:00)
-
23
def beginning_of_hour
-
change(min: 0)
-
end
-
23
alias :at_beginning_of_hour :beginning_of_hour
-
-
# Returns a new Time representing the end of the hour, x:59:59.999999
-
23
def end_of_hour
-
change(
-
min: 59,
-
sec: 59,
-
usec: Rational(999999999, 1000)
-
)
-
end
-
23
alias :at_end_of_hour :end_of_hour
-
-
# Returns a new Time representing the start of the minute (x:xx:00)
-
23
def beginning_of_minute
-
change(sec: 0)
-
end
-
23
alias :at_beginning_of_minute :beginning_of_minute
-
-
# Returns a new Time representing the end of the minute, x:xx:59.999999
-
23
def end_of_minute
-
change(
-
sec: 59,
-
usec: Rational(999999999, 1000)
-
)
-
end
-
23
alias :at_end_of_minute :end_of_minute
-
-
23
def plus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.since(self)
-
else
-
plus_without_duration(other)
-
end
-
end
-
23
alias_method :plus_without_duration, :+
-
23
alias_method :+, :plus_with_duration
-
-
23
def minus_with_duration(other) #:nodoc:
-
if ActiveSupport::Duration === other
-
other.until(self)
-
else
-
minus_without_duration(other)
-
end
-
end
-
23
alias_method :minus_without_duration, :-
-
23
alias_method :-, :minus_with_duration
-
-
# Time#- can also be used to determine the number of seconds between two Time instances.
-
# We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
-
# are coerced into values that Time#- will recognize
-
23
def minus_with_coercion(other)
-
other = other.comparable_time if other.respond_to?(:comparable_time)
-
other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other)
-
end
-
23
alias_method :minus_without_coercion, :-
-
23
alias_method :-, :minus_with_coercion
-
-
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
-
# can be chronologically compared with a Time
-
23
def compare_with_coercion(other)
-
# we're avoiding Time#to_datetime and Time#to_time because they're expensive
-
if other.class == Time
-
compare_without_coercion(other)
-
elsif other.is_a?(Time)
-
compare_without_coercion(other.to_time)
-
else
-
to_datetime <=> other
-
end
-
end
-
23
alias_method :compare_without_coercion, :<=>
-
23
alias_method :<=>, :compare_with_coercion
-
-
# Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances
-
# can be eql? to an equivalent Time
-
23
def eql_with_coercion(other)
-
# if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison
-
other = other.comparable_time if other.respond_to?(:comparable_time)
-
eql_without_coercion(other)
-
end
-
23
alias_method :eql_without_coercion, :eql?
-
23
alias_method :eql?, :eql_with_coercion
-
-
# Returns a new time the specified number of days ago.
-
23
def prev_day(days = 1)
-
advance(days: -days)
-
end
-
-
# Returns a new time the specified number of days in the future.
-
23
def next_day(days = 1)
-
advance(days: days)
-
end
-
-
# Returns a new time the specified number of months ago.
-
23
def prev_month(months = 1)
-
advance(months: -months)
-
end
-
-
# Returns a new time the specified number of months in the future.
-
23
def next_month(months = 1)
-
advance(months: months)
-
end
-
-
# Returns a new time the specified number of years ago.
-
23
def prev_year(years = 1)
-
advance(years: -years)
-
end
-
-
# Returns a new time the specified number of years in the future.
-
23
def next_year(years = 1)
-
advance(years: years)
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/date_and_time/compatibility"
-
2
require "active_support/core_ext/module/redefine_method"
-
-
2
class Time
-
2
include DateAndTime::Compatibility
-
-
2
silence_redefinition_of_method :to_time
-
-
# Either return +self+ or the time in the local system timezone depending
-
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
-
2
def to_time
-
preserve_timezone ? self : getlocal
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/inflector/methods"
-
23
require "active_support/values/time_zone"
-
-
23
class Time
-
23
DATE_FORMATS = {
-
db: "%Y-%m-%d %H:%M:%S",
-
inspect: "%Y-%m-%d %H:%M:%S.%9N %z",
-
number: "%Y%m%d%H%M%S",
-
nsec: "%Y%m%d%H%M%S%9N",
-
usec: "%Y%m%d%H%M%S%6N",
-
time: "%H:%M",
-
short: "%d %b %H:%M",
-
long: "%B %d, %Y %H:%M",
-
long_ordinal: lambda { |time|
-
day_format = ActiveSupport::Inflector.ordinalize(time.day)
-
time.strftime("%B #{day_format}, %Y %H:%M")
-
},
-
rfc822: lambda { |time|
-
offset_format = time.formatted_offset(false)
-
time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
-
},
-
iso8601: lambda { |time| time.iso8601 }
-
}
-
-
# Converts to a formatted string. See DATE_FORMATS for built-in formats.
-
#
-
# This method is aliased to <tt>to_s</tt>.
-
#
-
# time = Time.now # => 2007-01-18 06:10:17 -06:00
-
#
-
# time.to_formatted_s(:time) # => "06:10"
-
# time.to_s(:time) # => "06:10"
-
#
-
# time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
-
# time.to_formatted_s(:number) # => "20070118061017"
-
# time.to_formatted_s(:short) # => "18 Jan 06:10"
-
# time.to_formatted_s(:long) # => "January 18, 2007 06:10"
-
# time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
-
# time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
-
# time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00"
-
#
-
# == Adding your own time formats to +to_formatted_s+
-
# You can add your own formats to the Time::DATE_FORMATS hash.
-
# Use the format name as the hash key and either a strftime string
-
# or Proc instance that takes a time argument as the value.
-
#
-
# # config/initializers/time_formats.rb
-
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
-
# Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") }
-
23
def to_formatted_s(format = :default)
-
if formatter = DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
to_default_s
-
end
-
end
-
23
alias_method :to_default_s, :to_s
-
23
alias_method :to_s, :to_formatted_s
-
-
# Returns a formatted string of the offset from UTC, or an alternative
-
# string if the time zone is already UTC.
-
#
-
# Time.local(2000).formatted_offset # => "-06:00"
-
# Time.local(2000).formatted_offset(false) # => "-0600"
-
23
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Aliased to +xmlschema+ for compatibility with +DateTime+
-
23
alias_method :rfc3339, :xmlschema
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/time_with_zone"
-
23
require "active_support/core_ext/time/acts_like"
-
23
require "active_support/core_ext/date_and_time/zones"
-
-
23
class Time
-
23
include DateAndTime::Zones
-
23
class << self
-
23
attr_accessor :zone_default
-
-
# Returns the TimeZone for the current request, if this has been set (via Time.zone=).
-
# If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>.
-
23
def zone
-
Thread.current[:time_zone] || zone_default
-
end
-
-
# Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread.
-
#
-
# This method accepts any of the following:
-
#
-
# * A Rails TimeZone object.
-
# * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
-
# * A TZInfo::Timezone object.
-
# * An identifier for a TZInfo::Timezone object (e.g., "America/New_York").
-
#
-
# Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
-
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
-
#
-
# class ApplicationController < ActionController::Base
-
# around_action :set_time_zone
-
#
-
# def set_time_zone
-
# if logged_in?
-
# Time.use_zone(current_user.time_zone) { yield }
-
# else
-
# yield
-
# end
-
# end
-
# end
-
23
def zone=(time_zone)
-
Thread.current[:time_zone] = find_zone!(time_zone)
-
end
-
-
# Allows override of <tt>Time.zone</tt> locally inside supplied block;
-
# resets <tt>Time.zone</tt> to existing value when done.
-
#
-
# class ApplicationController < ActionController::Base
-
# around_action :set_time_zone
-
#
-
# private
-
#
-
# def set_time_zone
-
# Time.use_zone(current_user.timezone) { yield }
-
# end
-
# end
-
#
-
# NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
-
# objects that have already been created, e.g. any model timestamp
-
# attributes that have been read before the block will remain in
-
# the application's default timezone.
-
23
def use_zone(time_zone)
-
new_zone = find_zone!(time_zone)
-
begin
-
old_zone, ::Time.zone = ::Time.zone, new_zone
-
yield
-
ensure
-
::Time.zone = old_zone
-
end
-
end
-
-
# Returns a TimeZone instance matching the time zone provided.
-
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
-
# Raises an +ArgumentError+ for invalid time zones.
-
#
-
# Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
-
# Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
-
# Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
-
# Time.find_zone! nil # => nil
-
# Time.find_zone! false # => false
-
# Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
-
23
def find_zone!(time_zone)
-
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
-
time_zone
-
else
-
# Look up the timezone based on the identifier (unless we've been
-
# passed a TZInfo::Timezone)
-
unless time_zone.respond_to?(:period_for_local)
-
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
-
end
-
-
# Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
-
if time_zone.is_a?(ActiveSupport::TimeZone)
-
time_zone
-
else
-
ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
-
end
-
end
-
rescue TZInfo::InvalidTimezoneIdentifier
-
raise ArgumentError, "Invalid Timezone: #{time_zone}"
-
end
-
-
# Returns a TimeZone instance matching the time zone provided.
-
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
-
# Returns +nil+ for invalid time zones.
-
#
-
# Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
-
# Time.find_zone "NOT-A-TIMEZONE" # => nil
-
23
def find_zone(time_zone)
-
find_zone!(time_zone) rescue nil
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "uri"
-
-
1
if RUBY_VERSION < "2.6.0"
-
1
require "active_support/core_ext/module/redefine_method"
-
1
URI::Parser.class_eval do
-
1
silence_redefinition_of_method :unescape
-
1
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
-
# TODO: Are we actually sure that ASCII == UTF-8?
-
# YK: My initial experiments say yes, but let's be sure please
-
enc = str.encoding
-
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
-
str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc)
-
end
-
end
-
end
-
-
1
module URI
-
1
class << self
-
1
def parser
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
URI.parser is deprecated and will be removed in Rails 6.2.
-
Use `URI::DEFAULT_PARSER` instead.
-
MSG
-
URI::DEFAULT_PARSER
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/callbacks"
-
1
require "active_support/core_ext/enumerable"
-
-
1
module ActiveSupport
-
# Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
-
# before and after each request. This allows you to keep all the per-request attributes easily
-
# available to the whole system.
-
#
-
# The following full app-like example demonstrates how to use a Current class to
-
# facilitate easy access to the global, per-request attributes without passing them deeply
-
# around everywhere:
-
#
-
# # app/models/current.rb
-
# class Current < ActiveSupport::CurrentAttributes
-
# attribute :account, :user
-
# attribute :request_id, :user_agent, :ip_address
-
#
-
# resets { Time.zone = nil }
-
#
-
# def user=(user)
-
# super
-
# self.account = user.account
-
# Time.zone = user.time_zone
-
# end
-
# end
-
#
-
# # app/controllers/concerns/authentication.rb
-
# module Authentication
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# before_action :authenticate
-
# end
-
#
-
# private
-
# def authenticate
-
# if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
-
# Current.user = authenticated_user
-
# else
-
# redirect_to new_session_url
-
# end
-
# end
-
# end
-
#
-
# # app/controllers/concerns/set_current_request_details.rb
-
# module SetCurrentRequestDetails
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# before_action do
-
# Current.request_id = request.uuid
-
# Current.user_agent = request.user_agent
-
# Current.ip_address = request.ip
-
# end
-
# end
-
# end
-
#
-
# class ApplicationController < ActionController::Base
-
# include Authentication
-
# include SetCurrentRequestDetails
-
# end
-
#
-
# class MessagesController < ApplicationController
-
# def create
-
# Current.account.messages.create(message_params)
-
# end
-
# end
-
#
-
# class Message < ApplicationRecord
-
# belongs_to :creator, default: -> { Current.user }
-
# after_create { |message| Event.create(record: message) }
-
# end
-
#
-
# class Event < ApplicationRecord
-
# before_create do
-
# self.request_id = Current.request_id
-
# self.user_agent = Current.user_agent
-
# self.ip_address = Current.ip_address
-
# end
-
# end
-
#
-
# A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
-
# Current should only be used for a few, top-level globals, like account, user, and request details.
-
# The attributes stuck in Current should be used by more or less all actions on all requests. If you start
-
# sticking controller-specific attributes in there, you're going to create a mess.
-
1
class CurrentAttributes
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :reset
-
-
1
class << self
-
# Returns singleton instance for this class in this thread. If none exists, one is created.
-
1
def instance
-
2
current_instances[current_instances_key] ||= new
-
end
-
-
# Declares one or more attributes that will be given both class and instance accessor methods.
-
1
def attribute(*names)
-
2
generated_attribute_methods.module_eval do
-
2
names.each do |name|
-
6
define_method(name) do
-
attributes[name.to_sym]
-
end
-
-
6
define_method("#{name}=") do |attribute|
-
attributes[name.to_sym] = attribute
-
end
-
end
-
end
-
-
2
names.each do |name|
-
6
define_singleton_method(name) do
-
instance.public_send(name)
-
end
-
-
6
define_singleton_method("#{name}=") do |attribute|
-
instance.public_send("#{name}=", attribute)
-
end
-
end
-
end
-
-
# Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
-
1
def before_reset(&block)
-
1
set_callback :reset, :before, &block
-
end
-
-
# Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
-
1
def resets(&block)
-
1
set_callback :reset, :after, &block
-
end
-
1
alias_method :after_reset, :resets
-
-
1
delegate :set, :reset, to: :instance
-
-
1
def reset_all # :nodoc:
-
current_instances.each_value(&:reset)
-
end
-
-
1
def clear_all # :nodoc:
-
reset_all
-
current_instances.clear
-
end
-
-
1
private
-
1
def generated_attribute_methods
-
4
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
-
end
-
-
1
def current_instances
-
2
Thread.current[:current_attributes_instances] ||= {}
-
end
-
-
1
def current_instances_key
-
2
@current_instances_key ||= name.to_sym
-
end
-
-
1
def method_missing(name, *args, &block)
-
# Caches the method definition as a singleton method of the receiver.
-
#
-
# By letting #delegate handle it, we avoid an enclosure that'll capture args.
-
singleton_class.delegate name, to: :instance
-
-
send(name, *args, &block)
-
end
-
end
-
-
1
attr_accessor :attributes
-
-
1
def initialize
-
2
@attributes = {}
-
end
-
-
# Expose one or more attributes within a block. Old values are returned after the block concludes.
-
# Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
-
#
-
# class Chat::PublicationJob < ApplicationJob
-
# def perform(attributes, room_number, creator)
-
# Current.set(person: creator) do
-
# Chat::Publisher.publish(attributes: attributes, room_number: room_number)
-
# end
-
# end
-
# end
-
1
def set(set_attributes)
-
old_attributes = compute_attributes(set_attributes.keys)
-
assign_attributes(set_attributes)
-
yield
-
ensure
-
assign_attributes(old_attributes)
-
end
-
-
# Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
-
1
def reset
-
run_callbacks :reset do
-
self.attributes = {}
-
end
-
end
-
-
1
private
-
1
def assign_attributes(new_attributes)
-
new_attributes.each { |key, value| public_send("#{key}=", value) }
-
end
-
-
1
def compute_attributes(keys)
-
keys.index_with { |key| public_send(key) }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module ActiveSupport::CurrentAttributes::TestHelper # :nodoc:
-
1
def before_setup
-
ActiveSupport::CurrentAttributes.reset_all
-
super
-
end
-
-
1
def before_teardown
-
ActiveSupport::CurrentAttributes.reset_all
-
super
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "set"
-
3
require "thread"
-
3
require "concurrent/map"
-
3
require "pathname"
-
3
require "active_support/core_ext/module/aliasing"
-
3
require "active_support/core_ext/module/attribute_accessors"
-
3
require "active_support/core_ext/module/introspection"
-
3
require "active_support/core_ext/module/anonymous"
-
3
require "active_support/core_ext/object/blank"
-
3
require "active_support/core_ext/kernel/reporting"
-
3
require "active_support/core_ext/load_error"
-
3
require "active_support/core_ext/name_error"
-
3
require "active_support/dependencies/interlock"
-
3
require "active_support/inflector"
-
-
3
module ActiveSupport #:nodoc:
-
3
module Dependencies #:nodoc:
-
3
extend self
-
-
3
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
-
3
private_constant :UNBOUND_METHOD_MODULE_NAME
-
-
3
mattr_accessor :interlock, default: Interlock.new
-
-
# :doc:
-
-
# Execute the supplied block without interference from any
-
# concurrent loads.
-
3
def self.run_interlock
-
Dependencies.interlock.running { yield }
-
end
-
-
# Execute the supplied block while holding an exclusive lock,
-
# preventing any other thread from being inside a #run_interlock
-
# block at the same time.
-
3
def self.load_interlock
-
Dependencies.interlock.loading { yield }
-
end
-
-
# Execute the supplied block while holding an exclusive lock,
-
# preventing any other thread from being inside a #run_interlock
-
# block at the same time.
-
3
def self.unload_interlock
-
Dependencies.interlock.unloading { yield }
-
end
-
-
# :nodoc:
-
-
# Should we turn on Ruby warnings on the first load of dependent files?
-
3
mattr_accessor :warnings_on_first_load, default: false
-
-
# All files ever loaded.
-
3
mattr_accessor :history, default: Set.new
-
-
# All files currently loaded.
-
3
mattr_accessor :loaded, default: Set.new
-
-
# Stack of files being loaded.
-
3
mattr_accessor :loading, default: []
-
-
# Should we load files or require them?
-
3
mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load
-
-
# The set of directories from which we may automatically load files. Files
-
# under these directories will be reloaded on each request in development mode,
-
# unless the directory also appears in autoload_once_paths.
-
3
mattr_accessor :autoload_paths, default: []
-
-
# The set of directories from which automatically loaded constants are loaded
-
# only once. All directories in this set must also be present in +autoload_paths+.
-
3
mattr_accessor :autoload_once_paths, default: []
-
-
# This is a private set that collects all eager load paths during bootstrap.
-
# Useful for Zeitwerk integration. Its public interface is the config.* path
-
# accessors of each engine.
-
3
mattr_accessor :_eager_load_paths, default: Set.new
-
-
# An array of qualified constant names that have been loaded. Adding a name
-
# to this array will cause it to be unloaded the next time Dependencies are
-
# cleared.
-
3
mattr_accessor :autoloaded_constants, default: []
-
-
# An array of constant names that need to be unloaded on every request. Used
-
# to allow arbitrary constants to be marked for unloading.
-
3
mattr_accessor :explicitly_unloadable_constants, default: []
-
-
# The logger used when tracing autoloads.
-
3
mattr_accessor :logger
-
-
# If true, trace autoloads with +logger.debug+.
-
3
mattr_accessor :verbose, default: false
-
-
# The WatchStack keeps a stack of the modules being watched as files are
-
# loaded. If a file in the process of being loaded (parent.rb) triggers the
-
# load of another file (child.rb) the stack will ensure that child.rb
-
# handles the new constants.
-
#
-
# If child.rb is being autoloaded, its constants will be added to
-
# autoloaded_constants. If it was being required, they will be discarded.
-
#
-
# This is handled by walking back up the watch stack and adding the constants
-
# found by child.rb to the list of original constants in parent.rb.
-
3
class WatchStack
-
3
include Enumerable
-
-
# @watching is a stack of lists of constants being watched. For instance,
-
# if parent.rb is autoloaded, the stack will look like [[Object]]. If
-
# parent.rb then requires namespace/child.rb, the stack will look like
-
# [[Object], [Namespace]].
-
-
3
attr_reader :watching
-
-
3
def initialize
-
3
@watching = []
-
3
@stack = Hash.new { |h, k| h[k] = [] }
-
end
-
-
3
def each(&block)
-
@stack.each(&block)
-
end
-
-
3
def watching?
-
!@watching.empty?
-
end
-
-
# Returns a list of new constants found since the last call to
-
# <tt>watch_namespaces</tt>.
-
3
def new_constants
-
constants = []
-
-
# Grab the list of namespaces that we're looking for new constants under
-
@watching.last.each do |namespace|
-
# Retrieve the constants that were present under the namespace when watch_namespaces
-
# was originally called
-
original_constants = @stack[namespace].last
-
-
mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
-
next unless mod.is_a?(Module)
-
-
# Get a list of the constants that were added
-
new_constants = mod.constants(false) - original_constants
-
-
# @stack[namespace] returns an Array of the constants that are being evaluated
-
# for that namespace. For instance, if parent.rb requires child.rb, the first
-
# element of @stack[Object] will be an Array of the constants that were present
-
# before parent.rb was required. The second element will be an Array of the
-
# constants that were present before child.rb was required.
-
@stack[namespace].each do |namespace_constants|
-
namespace_constants.concat(new_constants)
-
end
-
-
# Normalize the list of new constants, and add them to the list we will return
-
new_constants.each do |suffix|
-
constants << ([namespace, suffix] - ["Object"]).join("::")
-
end
-
end
-
constants
-
ensure
-
# A call to new_constants is always called after a call to watch_namespaces
-
pop_modules(@watching.pop)
-
end
-
-
# Add a set of modules to the watch stack, remembering the initial
-
# constants.
-
3
def watch_namespaces(namespaces)
-
@watching << namespaces.map do |namespace|
-
module_name = Dependencies.to_constant_name(namespace)
-
original_constants = Dependencies.qualified_const_defined?(module_name) ?
-
Inflector.constantize(module_name).constants(false) : []
-
-
@stack[module_name] << original_constants
-
module_name
-
end
-
end
-
-
3
private
-
3
def pop_modules(modules)
-
modules.each { |mod| @stack[mod].pop }
-
end
-
end
-
-
# An internal stack used to record which constants are loaded by any block.
-
3
mattr_accessor :constant_watch_stack, default: WatchStack.new
-
-
# Module includes this module.
-
3
module ModuleConstMissing #:nodoc:
-
3
def self.append_features(base)
-
6
base.class_eval do
-
# Emulate #exclude via an ivar
-
6
return if defined?(@_const_missing) && @_const_missing
-
3
@_const_missing = instance_method(:const_missing)
-
3
remove_method(:const_missing)
-
end
-
3
super
-
end
-
-
3
def self.exclude_from(base)
-
base.class_eval do
-
define_method :const_missing, @_const_missing
-
@_const_missing = nil
-
end
-
end
-
-
3
def self.include_into(base)
-
3
base.include(self)
-
3
append_features(base)
-
end
-
-
3
def const_missing(const_name)
-
from_mod = anonymous? ? guess_for_anonymous(const_name) : self
-
Dependencies.load_missing_constant(from_mod, const_name)
-
end
-
-
# We assume that the name of the module reflects the nesting
-
# (unless it can be proven that is not the case) and the path to the file
-
# that defines the constant. Anonymous modules cannot follow these
-
# conventions and therefore we assume that the user wants to refer to a
-
# top-level constant.
-
3
def guess_for_anonymous(const_name)
-
if Object.const_defined?(const_name)
-
raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name
-
else
-
Object
-
end
-
end
-
-
3
def unloadable(const_desc = self)
-
super(const_desc)
-
end
-
end
-
-
# Object includes this module.
-
3
module Loadable #:nodoc:
-
3
def self.exclude_from(base)
-
base.class_eval do
-
define_method(:load, Kernel.instance_method(:load))
-
private :load
-
-
define_method(:require, Kernel.instance_method(:require))
-
private :require
-
end
-
end
-
-
3
def self.include_into(base)
-
3
base.include(self)
-
-
3
if base.instance_method(:load).owner == base
-
base.remove_method(:load)
-
end
-
-
3
if base.instance_method(:require).owner == base
-
base.remove_method(:require)
-
end
-
end
-
-
3
def require_or_load(file_name)
-
Dependencies.require_or_load(file_name)
-
end
-
-
# :doc:
-
-
# <b>Warning:</b> This method is obsolete in +:zeitwerk+ mode. In
-
# +:zeitwerk+ mode semantics match Ruby's and you do not need to be
-
# defensive with load order. Just refer to classes and modules normally.
-
# If the constant name is dynamic, camelize if needed, and constantize.
-
#
-
# In +:classic+ mode, interprets a file using +mechanism+ and marks its
-
# defined constants as autoloaded. +file_name+ can be either a string or
-
# respond to <tt>to_path</tt>.
-
#
-
# In +:classic+ mode, use this method in code that absolutely needs a
-
# certain constant to be defined at that point. A typical use case is to
-
# make constant name resolution deterministic for constants with the same
-
# relative name in different namespaces whose evaluation would depend on
-
# load order otherwise.
-
#
-
# Engines that do not control the mode in which their parent application
-
# runs should call +require_dependency+ where needed in case the runtime
-
# mode is +:classic+.
-
3
def require_dependency(file_name, message = "No such file to load -- %s.rb")
-
file_name = file_name.to_path if file_name.respond_to?(:to_path)
-
unless file_name.is_a?(String)
-
raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}"
-
end
-
-
Dependencies.depend_on(file_name, message)
-
end
-
-
# :nodoc:
-
-
3
def load_dependency(file)
-
767
if Dependencies.load? && Dependencies.constant_watch_stack.watching?
-
descs = Dependencies.constant_watch_stack.watching.flatten.uniq
-
-
Dependencies.new_constants_in(*descs) { yield }
-
else
-
767
yield
-
end
-
rescue Exception => exception # errors from loading file
-
4
exception.blame_file! file if exception.respond_to? :blame_file!
-
4
raise
-
end
-
-
# Mark the given constant as unloadable. Unloadable constants are removed
-
# each time dependencies are cleared.
-
#
-
# Note that marking a constant for unloading need only be done once. Setup
-
# or init scripts may list each unloadable constant that may need unloading;
-
# each constant will be removed for every subsequent clear, as opposed to
-
# for the first clear.
-
#
-
# The provided constant descriptor may be a (non-anonymous) module or class,
-
# or a qualified constant name as a string or symbol.
-
#
-
# Returns +true+ if the constant was not previously marked for unloading,
-
# +false+ otherwise.
-
3
def unloadable(const_desc)
-
Dependencies.mark_for_unload const_desc
-
end
-
-
3
private
-
3
def load(file, wrap = false)
-
result = false
-
load_dependency(file) { result = super }
-
result
-
end
-
-
3
def require(file)
-
767
result = false
-
1534
load_dependency(file) { result = super }
-
763
result
-
end
-
end
-
-
# Exception file-blaming.
-
3
module Blamable #:nodoc:
-
3
def blame_file!(file)
-
4
(@blamed_files ||= []).unshift file
-
end
-
-
3
def blamed_files
-
@blamed_files ||= []
-
end
-
-
3
def describe_blame
-
return nil if blamed_files.empty?
-
"This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
-
end
-
-
3
def copy_blame!(exc)
-
@blamed_files = exc.blamed_files.clone
-
self
-
end
-
end
-
-
3
def hook!
-
3
Loadable.include_into(Object)
-
3
ModuleConstMissing.include_into(Module)
-
3
Exception.include(Blamable)
-
end
-
-
3
def unhook!
-
ModuleConstMissing.exclude_from(Module)
-
Loadable.exclude_from(Object)
-
end
-
-
3
def load?
-
767
mechanism == :load
-
end
-
-
3
def depend_on(file_name, message = "No such file to load -- %s.rb")
-
path = search_for_file(file_name)
-
require_or_load(path || file_name)
-
rescue LoadError => load_error
-
if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
-
load_error.message.replace(message % file_name)
-
load_error.copy_blame!(load_error)
-
end
-
raise
-
end
-
-
3
def clear
-
Dependencies.unload_interlock do
-
loaded.clear
-
loading.clear
-
remove_unloadable_constants!
-
end
-
end
-
-
3
def require_or_load(file_name, const_path = nil)
-
file_name = file_name.chomp(".rb")
-
expanded = File.expand_path(file_name)
-
return if loaded.include?(expanded)
-
-
Dependencies.load_interlock do
-
# Maybe it got loaded while we were waiting for our lock:
-
return if loaded.include?(expanded)
-
-
# Record that we've seen this file *before* loading it to avoid an
-
# infinite loop with mutual dependencies.
-
loaded << expanded
-
loading << expanded
-
-
begin
-
if load?
-
# Enable warnings if this file has not been loaded before and
-
# warnings_on_first_load is set.
-
load_args = ["#{file_name}.rb"]
-
load_args << const_path unless const_path.nil?
-
-
if !warnings_on_first_load || history.include?(expanded)
-
result = load_file(*load_args)
-
else
-
enable_warnings { result = load_file(*load_args) }
-
end
-
else
-
result = require file_name
-
end
-
rescue Exception
-
loaded.delete expanded
-
raise
-
ensure
-
loading.pop
-
end
-
-
# Record history *after* loading so first load gets warnings.
-
history << expanded
-
result
-
end
-
end
-
-
# Is the provided constant path defined?
-
3
def qualified_const_defined?(path)
-
Object.const_defined?(path, false)
-
end
-
-
# Given +path+, a filesystem path to a ruby file, return an array of
-
# constant paths which would cause Dependencies to attempt to load this
-
# file.
-
3
def loadable_constants_for_path(path, bases = autoload_paths)
-
path = path.chomp(".rb")
-
expanded_path = File.expand_path(path)
-
paths = []
-
-
bases.each do |root|
-
expanded_root = File.expand_path(root)
-
next unless expanded_path.start_with?(expanded_root)
-
-
root_size = expanded_root.size
-
next if expanded_path[root_size] != ?/
-
-
nesting = expanded_path[(root_size + 1)..-1]
-
paths << nesting.camelize unless nesting.blank?
-
end
-
-
paths.uniq!
-
paths
-
end
-
-
# Search for a file in autoload_paths matching the provided suffix.
-
3
def search_for_file(path_suffix)
-
path_suffix += ".rb" unless path_suffix.end_with?(".rb")
-
-
autoload_paths.each do |root|
-
path = File.join(root, path_suffix)
-
return path if File.file? path
-
end
-
nil # Gee, I sure wish we had first_match ;-)
-
end
-
-
# Does the provided path_suffix correspond to an autoloadable module?
-
# Instead of returning a boolean, the autoload base for this module is
-
# returned.
-
3
def autoloadable_module?(path_suffix)
-
autoload_paths.each do |load_path|
-
return load_path if File.directory? File.join(load_path, path_suffix)
-
end
-
nil
-
end
-
-
3
def load_once_path?(path)
-
# to_s works around a ruby issue where String#start_with?(Pathname)
-
# will raise a TypeError: no implicit conversion of Pathname into String
-
autoload_once_paths.any? { |base| path.start_with?(base.to_s) }
-
end
-
-
# Attempt to autoload the provided module name by searching for a directory
-
# matching the expected path suffix. If found, the module is created and
-
# assigned to +into+'s constants with the name +const_name+. Provided that
-
# the directory was loaded from a reloadable base path, it is added to the
-
# set of constants that are to be unloaded.
-
3
def autoload_module!(into, const_name, qualified_name, path_suffix)
-
return nil unless base_path = autoloadable_module?(path_suffix)
-
mod = Module.new
-
into.const_set const_name, mod
-
log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})")
-
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
-
autoloaded_constants.uniq!
-
mod
-
end
-
-
# Load the file at the provided path. +const_paths+ is a set of qualified
-
# constant names. When loading the file, Dependencies will watch for the
-
# addition of these constants. Each that is defined will be marked as
-
# autoloaded, and will be removed when Dependencies.clear is next called.
-
#
-
# If the second parameter is left off, then Dependencies will construct a
-
# set of names that the file at +path+ may define. See
-
# +loadable_constants_for_path+ for more details.
-
3
def load_file(path, const_paths = loadable_constants_for_path(path))
-
const_paths = [const_paths].compact unless const_paths.is_a? Array
-
parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object }
-
-
result = nil
-
newly_defined_paths = new_constants_in(*parent_paths) do
-
result = Kernel.load path
-
end
-
-
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
-
autoloaded_constants.uniq!
-
result
-
end
-
-
# Returns the constant path for the provided parent and constant name.
-
3
def qualified_name_for(mod, name)
-
mod_name = to_constant_name mod
-
mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}"
-
end
-
-
# Load the constant named +const_name+ which is missing from +from_mod+. If
-
# it is not possible to load the constant into from_mod, try its parent
-
# module using +const_missing+.
-
3
def load_missing_constant(from_mod, const_name)
-
unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
-
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
-
end
-
-
qualified_name = qualified_name_for(from_mod, const_name)
-
path_suffix = qualified_name.underscore
-
-
file_path = search_for_file(path_suffix)
-
-
if file_path
-
expanded = File.expand_path(file_path)
-
expanded.delete_suffix!(".rb")
-
-
if loading.include?(expanded)
-
raise "Circular dependency detected while autoloading constant #{qualified_name}"
-
else
-
require_or_load(expanded, qualified_name)
-
-
if from_mod.const_defined?(const_name, false)
-
log("constant #{qualified_name} autoloaded from #{expanded}.rb")
-
return from_mod.const_get(const_name)
-
else
-
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
-
end
-
end
-
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
-
return mod
-
elsif (parent = from_mod.module_parent) && parent != from_mod &&
-
! from_mod.module_parents.any? { |p| p.const_defined?(const_name, false) }
-
# If our parents do not have a constant named +const_name+ then we are free
-
# to attempt to load upwards. If they do have such a constant, then this
-
# const_missing must be due to from_mod::const_name, which should not
-
# return constants from from_mod's parents.
-
begin
-
# Since Ruby does not pass the nesting at the point the unknown
-
# constant triggered the callback we cannot fully emulate constant
-
# name lookup and need to make a trade-off: we are going to assume
-
# that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
-
# though it might not be. Counterexamples are
-
#
-
# class Foo::Bar
-
# Module.nesting # => [Foo::Bar]
-
# end
-
#
-
# or
-
#
-
# module M::N
-
# module S::T
-
# Module.nesting # => [S::T, M::N]
-
# end
-
# end
-
#
-
# for example.
-
return parent.const_missing(const_name)
-
rescue NameError => e
-
raise unless e.missing_name? qualified_name_for(parent, const_name)
-
end
-
end
-
-
name_error = uninitialized_constant(qualified_name, const_name, receiver: from_mod)
-
name_error.set_backtrace(caller.reject { |l| l.start_with? __FILE__ })
-
raise name_error
-
end
-
-
# Remove the constants that have been autoloaded, and those that have been
-
# marked for unloading. Before each constant is removed a callback is sent
-
# to its class/module if it implements +before_remove_const+.
-
#
-
# The callback implementation should be restricted to cleaning up caches, etc.
-
# as the environment will be in an inconsistent state, e.g. other constants
-
# may have already been unloaded and not accessible.
-
3
def remove_unloadable_constants!
-
log("removing unloadable constants")
-
autoloaded_constants.each { |const| remove_constant const }
-
autoloaded_constants.clear
-
Reference.clear!
-
explicitly_unloadable_constants.each { |const| remove_constant const }
-
end
-
-
3
class ClassCache
-
3
def initialize
-
3
@store = Concurrent::Map.new
-
end
-
-
3
def empty?
-
@store.empty?
-
end
-
-
3
def key?(key)
-
@store.key?(key)
-
end
-
-
3
def get(key)
-
key = key.name if key.respond_to?(:name)
-
@store[key] ||= Inflector.constantize(key)
-
end
-
3
alias :[] :get
-
-
3
def safe_get(key)
-
key = key.name if key.respond_to?(:name)
-
@store[key] ||= Inflector.safe_constantize(key)
-
end
-
-
3
def store(klass)
-
return self unless klass.respond_to?(:name)
-
raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty?
-
@store[klass.name] = klass
-
self
-
end
-
-
3
def clear!
-
@store.clear
-
end
-
end
-
-
3
Reference = ClassCache.new
-
-
# Store a reference to a class +klass+.
-
3
def reference(klass)
-
Reference.store klass
-
end
-
-
# Get the reference for class named +name+.
-
# Raises an exception if referenced class does not exist.
-
3
def constantize(name)
-
Reference.get(name)
-
end
-
-
# Get the reference for class named +name+ if one exists.
-
# Otherwise returns +nil+.
-
3
def safe_constantize(name)
-
Reference.safe_get(name)
-
end
-
-
# Determine if the given constant has been automatically loaded.
-
3
def autoloaded?(desc)
-
return false if desc.is_a?(Module) && real_mod_name(desc).nil?
-
name = to_constant_name desc
-
return false unless qualified_const_defined?(name)
-
autoloaded_constants.include?(name)
-
end
-
-
# Will the provided constant descriptor be unloaded?
-
3
def will_unload?(const_desc)
-
autoloaded?(const_desc) ||
-
explicitly_unloadable_constants.include?(to_constant_name(const_desc))
-
end
-
-
# Mark the provided constant name for unloading. This constant will be
-
# unloaded on each request, not just the next one.
-
3
def mark_for_unload(const_desc)
-
name = to_constant_name const_desc
-
if explicitly_unloadable_constants.include? name
-
false
-
else
-
explicitly_unloadable_constants << name
-
true
-
end
-
end
-
-
# Run the provided block and detect the new constants that were loaded during
-
# its execution. Constants may only be regarded as 'new' once -- so if the
-
# block calls +new_constants_in+ again, then the constants defined within the
-
# inner call will not be reported in this one.
-
#
-
# If the provided block does not run to completion, and instead raises an
-
# exception, any new constants are regarded as being only partially defined
-
# and will be removed immediately.
-
3
def new_constants_in(*descs)
-
constant_watch_stack.watch_namespaces(descs)
-
success = false
-
-
begin
-
yield # Now yield to the code that is to define new constants.
-
success = true
-
ensure
-
new_constants = constant_watch_stack.new_constants
-
-
return new_constants if success
-
-
# Remove partially loaded constants.
-
new_constants.each { |c| remove_constant(c) }
-
end
-
end
-
-
# Convert the provided const desc to a qualified constant name (as a string).
-
# A module, class, symbol, or string may be provided.
-
3
def to_constant_name(desc) #:nodoc:
-
case desc
-
when String then desc.delete_prefix("::")
-
when Symbol then desc.to_s
-
when Module
-
real_mod_name(desc) ||
-
raise(ArgumentError, "Anonymous modules have no name to be referenced by")
-
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
-
end
-
end
-
-
3
def remove_constant(const) #:nodoc:
-
# Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo.
-
normalized = const.to_s.delete_prefix("::")
-
normalized.sub!(/\A(Object::)+/, "")
-
-
constants = normalized.split("::")
-
to_remove = constants.pop
-
-
# Remove the file path from the loaded list.
-
file_path = search_for_file(const.underscore)
-
if file_path
-
expanded = File.expand_path(file_path)
-
expanded.delete_suffix!(".rb")
-
loaded.delete(expanded)
-
end
-
-
if constants.empty?
-
parent = Object
-
else
-
# This method is robust to non-reachable constants.
-
#
-
# Non-reachable constants may be passed if some of the parents were
-
# autoloaded and already removed. It is easier to do a sanity check
-
# here than require the caller to be clever. We check the parent
-
# rather than the very const argument because we do not want to
-
# trigger Kernel#autoloads, see the comment below.
-
parent_name = constants.join("::")
-
return unless qualified_const_defined?(parent_name)
-
parent = constantize(parent_name)
-
end
-
-
# In an autoloaded user.rb like this
-
#
-
# autoload :Foo, 'foo'
-
#
-
# class User < ActiveRecord::Base
-
# end
-
#
-
# we correctly register "Foo" as being autoloaded. But if the app does
-
# not use the "Foo" constant we need to be careful not to trigger
-
# loading "foo.rb" ourselves. While #const_defined? and #const_get? do
-
# require the file, #autoload? and #remove_const don't.
-
#
-
# We are going to remove the constant nonetheless ---which exists as
-
# far as Ruby is concerned--- because if the user removes the macro
-
# call from a class or module that were not autoloaded, as in the
-
# example above with Object, accessing to that constant must err.
-
unless parent.autoload?(to_remove)
-
begin
-
constantized = parent.const_get(to_remove, false)
-
rescue NameError
-
# The constant is no longer reachable, just skip it.
-
return
-
else
-
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
-
end
-
end
-
-
begin
-
parent.instance_eval { remove_const to_remove }
-
rescue NameError
-
# The constant is no longer reachable, just skip it.
-
end
-
end
-
-
3
def log(message)
-
logger.debug("autoloading: #{message}") if logger && verbose
-
end
-
-
3
private
-
3
if RUBY_VERSION < "2.6"
-
3
def uninitialized_constant(qualified_name, const_name, receiver:)
-
NameError.new("uninitialized constant #{qualified_name}", const_name)
-
end
-
else
-
def uninitialized_constant(qualified_name, const_name, receiver:)
-
NameError.new("uninitialized constant #{qualified_name}", const_name, receiver: receiver)
-
end
-
end
-
-
# Returns the original name of a class or module even if `name` has been
-
# overridden.
-
3
def real_mod_name(mod)
-
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
-
end
-
end
-
end
-
-
3
ActiveSupport::Dependencies.hook!
-
# frozen_string_literal: true
-
-
24
require "active_support/inflector/methods"
-
-
24
module ActiveSupport
-
# Autoload and eager load conveniences for your library.
-
#
-
# This module allows you to define autoloads based on
-
# Rails conventions (i.e. no need to define the path
-
# it is automatically guessed based on the filename)
-
# and also define a set of constants that needs to be
-
# eager loaded:
-
#
-
# module MyLib
-
# extend ActiveSupport::Autoload
-
#
-
# autoload :Model
-
#
-
# eager_autoload do
-
# autoload :Cache
-
# end
-
# end
-
#
-
# Then your library can be eager loaded by simply calling:
-
#
-
# MyLib.eager_load!
-
24
module Autoload
-
24
def self.extended(base) # :nodoc:
-
27
base.class_eval do
-
27
@_autoloads = {}
-
27
@_under_path = nil
-
27
@_at_path = nil
-
27
@_eager_autoload = false
-
end
-
end
-
-
24
def autoload(const_name, path = @_at_path)
-
1023
unless path
-
993
full = [name, @_under_path, const_name.to_s].compact.join("::")
-
993
path = Inflector.underscore(full)
-
end
-
-
1023
if @_eager_autoload
-
585
@_autoloads[const_name] = path
-
end
-
-
1023
super const_name, path
-
end
-
-
24
def autoload_under(path)
-
@_under_path, old_path = path, @_under_path
-
yield
-
ensure
-
@_under_path = old_path
-
end
-
-
24
def autoload_at(path)
-
@_at_path, old_path = path, @_at_path
-
yield
-
ensure
-
@_at_path = old_path
-
end
-
-
24
def eager_autoload
-
25
old_eager, @_eager_autoload = @_eager_autoload, true
-
25
yield
-
ensure
-
25
@_eager_autoload = old_eager
-
end
-
-
24
def eager_load!
-
@_autoloads.each_value { |file| require file }
-
end
-
-
24
def autoloads
-
@_autoloads
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/concurrency/share_lock"
-
-
3
module ActiveSupport #:nodoc:
-
3
module Dependencies #:nodoc:
-
3
class Interlock
-
3
def initialize # :nodoc:
-
3
@lock = ActiveSupport::Concurrency::ShareLock.new
-
end
-
-
3
def loading
-
@lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do
-
yield
-
end
-
end
-
-
3
def unloading
-
@lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do
-
yield
-
end
-
end
-
-
3
def start_unloading
-
@lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
-
end
-
-
3
def done_unloading
-
@lock.stop_exclusive(compatible: [:load, :unload])
-
end
-
-
3
def start_running
-
@lock.start_sharing
-
end
-
-
3
def done_running
-
@lock.stop_sharing
-
end
-
-
3
def running
-
@lock.sharing do
-
yield
-
end
-
end
-
-
3
def permit_concurrent_loads
-
@lock.yield_shares(compatible: [:load]) do
-
yield
-
end
-
end
-
-
3
def raw_state(&block) # :nodoc:
-
@lock.raw_state(&block)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "set"
-
1
require "active_support/core_ext/string/inflections"
-
-
1
module ActiveSupport
-
1
module Dependencies
-
1
module ZeitwerkIntegration # :nodoc: all
-
1
module Decorations
-
1
def clear
-
Dependencies.unload_interlock do
-
Rails.autoloaders.main.reload
-
rescue Zeitwerk::ReloadingDisabledError
-
raise "reloading is disabled because config.cache_classes is true"
-
end
-
end
-
-
1
def constantize(cpath)
-
ActiveSupport::Inflector.constantize(cpath)
-
end
-
-
1
def safe_constantize(cpath)
-
ActiveSupport::Inflector.safe_constantize(cpath)
-
end
-
-
1
def autoloaded_constants
-
Rails.autoloaders.main.unloadable_cpaths
-
end
-
-
1
def autoloaded?(object)
-
cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s
-
Rails.autoloaders.main.unloadable_cpath?(cpath)
-
end
-
-
1
def verbose=(verbose)
-
l = verbose ? logger || Rails.logger : nil
-
Rails.autoloaders.each { |autoloader| autoloader.logger = l }
-
end
-
-
1
def unhook!
-
:no_op
-
end
-
end
-
-
1
module RequireDependency
-
1
def require_dependency(filename)
-
filename = filename.to_path if filename.respond_to?(:to_path)
-
if abspath = ActiveSupport::Dependencies.search_for_file(filename)
-
require abspath
-
else
-
require filename
-
end
-
end
-
end
-
-
1
module Inflector
-
# Concurrent::Map is not needed. This is a private class, and overrides
-
# must be defined while the application boots.
-
1
@overrides = {}
-
-
1
def self.camelize(basename, _abspath)
-
@overrides[basename] || basename.camelize
-
end
-
-
1
def self.inflect(overrides)
-
@overrides.merge!(overrides)
-
end
-
end
-
-
1
class << self
-
1
def take_over(enable_reloading:)
-
setup_autoloaders(enable_reloading)
-
freeze_paths
-
decorate_dependencies
-
end
-
-
1
private
-
1
def setup_autoloaders(enable_reloading)
-
Dependencies.autoload_paths.each do |autoload_path|
-
# Zeitwerk only accepts existing directories in `push_dir` to
-
# prevent misconfigurations.
-
next unless File.directory?(autoload_path)
-
-
autoloader = \
-
autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main
-
-
autoloader.push_dir(autoload_path)
-
autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path)
-
end
-
-
Rails.autoloaders.main.enable_reloading if enable_reloading
-
Rails.autoloaders.each(&:setup)
-
end
-
-
1
def autoload_once?(autoload_path)
-
Dependencies.autoload_once_paths.include?(autoload_path)
-
end
-
-
1
def eager_load?(autoload_path)
-
Dependencies._eager_load_paths.member?(autoload_path)
-
end
-
-
1
def freeze_paths
-
Dependencies.autoload_paths.freeze
-
Dependencies.autoload_once_paths.freeze
-
Dependencies._eager_load_paths.freeze
-
end
-
-
1
def decorate_dependencies
-
Dependencies.unhook!
-
Dependencies.singleton_class.prepend(Decorations)
-
Object.prepend(RequireDependency)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "singleton"
-
-
24
module ActiveSupport
-
# \Deprecation specifies the API used by Rails to deprecate methods, instance
-
# variables, objects and constants.
-
24
class Deprecation
-
# active_support.rb sets an autoload for ActiveSupport::Deprecation.
-
#
-
# If these requires were at the top of the file the constant would not be
-
# defined by the time their files were loaded. Since some of them reopen
-
# ActiveSupport::Deprecation its autoload would be triggered, resulting in
-
# a circular require warning for active_support/deprecation.rb.
-
#
-
# So, we define the constant first, and load dependencies later.
-
24
require "active_support/deprecation/instance_delegator"
-
24
require "active_support/deprecation/behaviors"
-
24
require "active_support/deprecation/reporting"
-
24
require "active_support/deprecation/disallowed"
-
24
require "active_support/deprecation/constant_accessor"
-
24
require "active_support/deprecation/method_wrappers"
-
23
require "active_support/deprecation/proxy_wrappers"
-
23
require "active_support/core_ext/module/deprecation"
-
23
require "concurrent/atomic/thread_local_var"
-
-
23
include Singleton
-
23
include InstanceDelegator
-
23
include Behavior
-
23
include Reporting
-
23
include Disallowed
-
23
include MethodWrapper
-
-
# The version number in which the deprecated behavior will be removed, by default.
-
23
attr_accessor :deprecation_horizon
-
-
# It accepts two parameters on initialization. The first is a version of library
-
# and the second is a library name.
-
#
-
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
-
23
def initialize(deprecation_horizon = "6.2", gem_name = "Rails")
-
23
self.gem_name = gem_name
-
23
self.deprecation_horizon = deprecation_horizon
-
# By default, warnings are not silenced and debugging is off.
-
23
self.silenced = false
-
23
self.debug = false
-
23
@silenced_thread = Concurrent::ThreadLocalVar.new(false)
-
23
@explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/notifications"
-
-
24
module ActiveSupport
-
# Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
-
# You would set <tt>:raise</tt>, as a behavior to raise errors and proactively report exceptions from deprecations.
-
24
class DeprecationException < StandardError
-
end
-
-
24
class Deprecation
-
# Default warning behaviors per Rails.env.
-
24
DEFAULT_BEHAVIORS = {
-
raise: ->(message, callstack, deprecation_horizon, gem_name) {
-
e = DeprecationException.new(message)
-
e.set_backtrace(callstack.map(&:to_s))
-
raise e
-
},
-
-
stderr: ->(message, callstack, deprecation_horizon, gem_name) {
-
$stderr.puts(message)
-
$stderr.puts callstack.join("\n ") if debug
-
},
-
-
log: ->(message, callstack, deprecation_horizon, gem_name) {
-
logger =
-
if defined?(Rails.logger) && Rails.logger
-
Rails.logger
-
else
-
require "active_support/logger"
-
ActiveSupport::Logger.new($stderr)
-
end
-
logger.warn message
-
logger.debug callstack.join("\n ") if debug
-
},
-
-
notify: ->(message, callstack, deprecation_horizon, gem_name) {
-
notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}"
-
ActiveSupport::Notifications.instrument(notification_name,
-
message: message,
-
callstack: callstack,
-
gem_name: gem_name,
-
deprecation_horizon: deprecation_horizon)
-
},
-
-
silence: ->(message, callstack, deprecation_horizon, gem_name) { },
-
}
-
-
# Behavior module allows to determine how to display deprecation messages.
-
# You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
-
# constant. Available behaviors are:
-
#
-
# [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
-
# [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
-
# [+log+] Log all deprecation warnings to +Rails.logger+.
-
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
-
# [+silence+] Do nothing.
-
#
-
# Setting behaviors only affects deprecations that happen after boot time.
-
# For more information you can read the documentation of the +behavior=+ method.
-
24
module Behavior
-
# Whether to print a backtrace along with the warning.
-
24
attr_accessor :debug
-
-
# Returns the current behavior or if one isn't set, defaults to +:stderr+.
-
24
def behavior
-
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
-
end
-
-
# Returns the current behavior for disallowed deprecations or if one isn't set, defaults to +:raise+.
-
24
def disallowed_behavior
-
@disallowed_behavior ||= [DEFAULT_BEHAVIORS[:raise]]
-
end
-
-
# Sets the behavior to the specified value. Can be a single value, array,
-
# or an object that responds to +call+.
-
#
-
# Available behaviors:
-
#
-
# [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
-
# [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
-
# [+log+] Log all deprecation warnings to +Rails.logger+.
-
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
-
# [+silence+] Do nothing.
-
#
-
# Setting behaviors only affects deprecations that happen after boot time.
-
# Deprecation warnings raised by gems are not affected by this setting
-
# because they happen before Rails boots up.
-
#
-
# ActiveSupport::Deprecation.behavior = :stderr
-
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
-
# ActiveSupport::Deprecation.behavior = MyCustomHandler
-
# ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) {
-
# # custom stuff
-
# }
-
24
def behavior=(behavior)
-
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
-
end
-
-
# Sets the behavior for disallowed deprecations (those configured by
-
# ActiveSupport::Deprecation.disallowed_warnings=) to the specified
-
# value. As with +behavior=+, this can be a single value, array, or an
-
# object that responds to +call+.
-
24
def disallowed_behavior=(behavior)
-
@disallowed_behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
-
end
-
-
24
private
-
24
def arity_coerce(behavior)
-
unless behavior.respond_to?(:call)
-
raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior."
-
end
-
-
if behavior.arity == 4 || behavior.arity == -1
-
behavior
-
else
-
-> message, callstack, _, _ { behavior.call(message, callstack) }
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
module ActiveSupport
-
24
class Deprecation
-
# DeprecatedConstantAccessor transforms a constant into a deprecated one by
-
# hooking +const_missing+.
-
#
-
# It takes the names of an old (deprecated) constant and of a new constant
-
# (both in string form) and optionally a deprecator. The deprecator defaults
-
# to +ActiveSupport::Deprecator+ if none is specified.
-
#
-
# The deprecated constant now returns the same object as the new one rather
-
# than a proxy object, so it can be used transparently in +rescue+ blocks
-
# etc.
-
#
-
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
-
#
-
# # (In a later update, the original implementation of `PLANETS` has been removed.)
-
#
-
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
-
# include ActiveSupport::Deprecation::DeprecatedConstantAccessor
-
# deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
-
#
-
# PLANETS.map { |planet| planet.capitalize }
-
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
-
# (Backtrace information…)
-
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
-
24
module DeprecatedConstantAccessor
-
24
def self.included(base)
-
1
require "active_support/inflector/methods"
-
-
1
extension = Module.new do
-
1
def const_missing(missing_const_name)
-
if class_variable_defined?(:@@_deprecated_constants)
-
if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
-
replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations)
-
return ActiveSupport::Inflector.constantize(replacement[:new].to_s)
-
end
-
end
-
super
-
end
-
-
1
def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
-
2
class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
-
2
class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
-
end
-
end
-
1
base.singleton_class.prepend extension
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
module ActiveSupport
-
24
class Deprecation
-
24
module Disallowed
-
# Sets the criteria used to identify deprecation messages which should be
-
# disallowed. Can be an array containing strings, symbols, or regular
-
# expressions. (Symbols are treated as strings). These are compared against
-
# the text of the generated deprecation warning.
-
#
-
# Additionally the scalar symbol +:all+ may be used to treat all
-
# deprecations as disallowed.
-
#
-
# Deprecations matching a substring or regular expression will be handled
-
# using the configured +ActiveSupport::Deprecation.disallowed_behavior+
-
# rather than +ActiveSupport::Deprecation.behavior+
-
24
attr_writer :disallowed_warnings
-
-
# Returns the configured criteria used to identify deprecation messages
-
# which should be treated as disallowed.
-
24
def disallowed_warnings
-
@disallowed_warnings ||= []
-
end
-
-
24
private
-
24
def deprecation_disallowed?(message)
-
disallowed = ActiveSupport::Deprecation.disallowed_warnings
-
return false if explicitly_allowed?(message)
-
return true if disallowed == :all
-
disallowed.any? do |rule|
-
case rule
-
when String, Symbol
-
message.include?(rule.to_s)
-
when Regexp
-
rule.match?(message)
-
end
-
end
-
end
-
-
24
def explicitly_allowed?(message)
-
allowances = @explicitly_allowed_warnings.value
-
return false unless allowances
-
return true if allowances == :all
-
allowances = [allowances] unless allowances.kind_of?(Array)
-
allowances.any? do |rule|
-
case rule
-
when String, Symbol
-
message.include?(rule.to_s)
-
when Regexp
-
rule.match?(message)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/core_ext/module/delegation"
-
-
24
module ActiveSupport
-
24
class Deprecation
-
24
module InstanceDelegator # :nodoc:
-
24
def self.included(base)
-
23
base.extend(ClassMethods)
-
23
base.singleton_class.prepend(OverrideDelegators)
-
23
base.public_class_method :new
-
end
-
-
24
module ClassMethods # :nodoc:
-
24
def include(included_module)
-
483
included_module.instance_methods.each { |m| method_added(m) }
-
92
super
-
end
-
-
24
def method_added(method_name)
-
460
singleton_class.delegate(method_name, to: :instance)
-
end
-
end
-
-
24
module OverrideDelegators # :nodoc:
-
24
def warn(message = nil, callstack = nil)
-
callstack ||= caller_locations(2)
-
super
-
end
-
-
24
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
-
caller_backtrace ||= caller_locations(2)
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/core_ext/array/extract_options"
-
24
require "active_support/core_ext/module/redefine_method"
-
-
23
module ActiveSupport
-
23
class Deprecation
-
23
module MethodWrapper
-
# Declare that a method has been deprecated.
-
#
-
# class Fred
-
# def aaa; end
-
# def bbb; end
-
# def ccc; end
-
# def ddd; end
-
# def eee; end
-
# end
-
#
-
# Using the default deprecator:
-
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
-
# # => Fred
-
#
-
# Fred.new.aaa
-
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
-
# # => nil
-
#
-
# Fred.new.bbb
-
# # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
-
# # => nil
-
#
-
# Fred.new.ccc
-
# # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
-
# # => nil
-
#
-
# Passing in a custom deprecator:
-
# custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
-
# ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
-
# # => [:ddd]
-
#
-
# Fred.new.ddd
-
# DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
-
# # => nil
-
#
-
# Using a custom deprecator directly:
-
# custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
-
# custom_deprecator.deprecate_methods(Fred, eee: :zzz)
-
# # => [:eee]
-
#
-
# Fred.new.eee
-
# DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
-
# # => nil
-
23
def deprecate_methods(target_module, *method_names)
-
4
options = method_names.extract_options!
-
4
deprecator = options.delete(:deprecator) || self
-
4
method_names += options.keys
-
4
mod = nil
-
-
4
method_names.each do |method_name|
-
9
message = options[method_name]
-
9
if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name)
-
8
method = target_module.instance_method(method_name)
-
8
target_module.module_eval do
-
8
redefine_method(method_name) do |*args, &block|
-
deprecator.deprecation_warning(method_name, message)
-
method.bind(self).call(*args, &block)
-
end
-
8
ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
-
end
-
else
-
1
mod ||= Module.new
-
1
mod.module_eval do
-
1
define_method(method_name) do |*args, &block|
-
deprecator.deprecation_warning(method_name, message)
-
super(*args, &block)
-
end
-
1
ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
-
end
-
end
-
end
-
-
4
target_module.prepend(mod) if mod
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport
-
23
class Deprecation
-
23
class DeprecationProxy #:nodoc:
-
23
def self.new(*args, &block)
-
object = args.first
-
-
return object unless object
-
super
-
end
-
-
1541
instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) }
-
-
# Don't give a deprecation warning on inspect since test/unit and error
-
# logs rely on it for diagnostics.
-
23
def inspect
-
target.inspect
-
end
-
-
23
private
-
23
def method_missing(called, *args, &block)
-
warn caller_locations, called, args
-
target.__send__(called, *args, &block)
-
end
-
end
-
-
# DeprecatedObjectProxy transforms an object into a deprecated one. It
-
# takes an object, a deprecation message and optionally a deprecator. The
-
# deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
-
#
-
# deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
-
# # => #<Object:0x007fb9b34c34b0>
-
#
-
# deprecated_object.to_s
-
# DEPRECATION WARNING: This object is now deprecated.
-
# (Backtrace)
-
# # => "#<Object:0x007fb9b34c34b0>"
-
23
class DeprecatedObjectProxy < DeprecationProxy
-
23
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
-
@object = object
-
@message = message
-
@deprecator = deprecator
-
end
-
-
23
private
-
23
def target
-
@object
-
end
-
-
23
def warn(callstack, called, args)
-
@deprecator.warn(@message, callstack)
-
end
-
end
-
-
# DeprecatedInstanceVariableProxy transforms an instance variable into a
-
# deprecated one. It takes an instance of a class, a method on that class
-
# and an instance variable. It optionally takes a deprecator as the last
-
# argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
-
# is specified.
-
#
-
# class Example
-
# def initialize
-
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
-
# @_request = :special_request
-
# end
-
#
-
# def request
-
# @_request
-
# end
-
#
-
# def old_request
-
# @request
-
# end
-
# end
-
#
-
# example = Example.new
-
# # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request>
-
#
-
# example.old_request.to_s
-
# # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
-
# @request.to_s
-
# (Backtrace information…)
-
# "special_request"
-
#
-
# example.request.to_s
-
# # => "special_request"
-
23
class DeprecatedInstanceVariableProxy < DeprecationProxy
-
23
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
-
@instance = instance
-
@method = method
-
@var = var
-
@deprecator = deprecator
-
end
-
-
23
private
-
23
def target
-
@instance.__send__(@method)
-
end
-
-
23
def warn(callstack, called, args)
-
@deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
-
end
-
end
-
-
# DeprecatedConstantProxy transforms a constant into a deprecated one. It
-
# takes the names of an old (deprecated) constant and of a new constant
-
# (both in string form) and optionally a deprecator. The deprecator defaults
-
# to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
-
# now returns the value of the new one.
-
#
-
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
-
#
-
# # (In a later update, the original implementation of `PLANETS` has been removed.)
-
#
-
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
-
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
-
#
-
# PLANETS.map { |planet| planet.capitalize }
-
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
-
# (Backtrace information…)
-
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
-
23
class DeprecatedConstantProxy < Module
-
23
def self.new(*args, **options, &block)
-
2
object = args.first
-
-
2
return object unless object
-
2
super
-
end
-
-
23
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
-
2
Kernel.require "active_support/inflector/methods"
-
-
2
@old_const = old_const
-
2
@new_const = new_const
-
2
@deprecator = deprecator
-
2
@message = message
-
end
-
-
2970
instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) }
-
-
# Don't give a deprecation warning on inspect since test/unit and error
-
# logs rely on it for diagnostics.
-
23
def inspect
-
target.inspect
-
end
-
-
# Don't give a deprecation warning on methods that IRB may invoke
-
# during tab-completion.
-
23
delegate :hash, :instance_methods, :name, :respond_to?, to: :target
-
-
# Returns the class of the new constant.
-
#
-
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
-
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
-
# PLANETS.class # => Array
-
23
def class
-
target.class
-
end
-
-
23
private
-
23
def target
-
ActiveSupport::Inflector.constantize(@new_const.to_s)
-
end
-
-
23
def const_missing(name)
-
@deprecator.warn(@message, caller_locations)
-
target.const_get(name)
-
end
-
-
23
def method_missing(called, *args, &block)
-
@deprecator.warn(@message, caller_locations)
-
target.__send__(called, *args, &block)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "rbconfig"
-
-
24
module ActiveSupport
-
24
class Deprecation
-
24
module Reporting
-
# Whether to print a message (silent mode)
-
24
attr_writer :silenced
-
# Name of gem where method is deprecated
-
24
attr_accessor :gem_name
-
-
# Outputs a deprecation warning to the output configured by
-
# <tt>ActiveSupport::Deprecation.behavior</tt>.
-
#
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
-
24
def warn(message = nil, callstack = nil)
-
return if silenced
-
-
callstack ||= caller_locations(2)
-
deprecation_message(callstack, message).tap do |m|
-
if deprecation_disallowed?(message)
-
disallowed_behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
-
else
-
behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
-
end
-
end
-
end
-
-
# Silence deprecation warnings within the block.
-
#
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
-
#
-
# ActiveSupport::Deprecation.silence do
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# end
-
# # => nil
-
24
def silence(&block)
-
@silenced_thread.bind(true, &block)
-
end
-
-
# Allow previously disallowed deprecation warnings within the block.
-
# <tt>allowed_warnings</tt> can be an array containing strings, symbols, or regular
-
# expressions. (Symbols are treated as strings). These are compared against
-
# the text of deprecation warning messages generated within the block.
-
# Matching warnings will be exempt from the rules set by
-
# +ActiveSupport::Deprecation.disallowed_warnings+
-
#
-
# The optional <tt>if:</tt> argument accepts a truthy/falsy value or an object that
-
# responds to <tt>.call</tt>. If truthy, then matching warnings will be allowed.
-
# If falsey then the method yields to the block without allowing the warning.
-
#
-
# ActiveSupport::Deprecation.disallowed_behavior = :raise
-
# ActiveSupport::Deprecation.disallowed_warnings = [
-
# "something broke"
-
# ]
-
#
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# # => ActiveSupport::DeprecationException
-
#
-
# ActiveSupport::Deprecation.allow ['something broke'] do
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# end
-
# # => nil
-
#
-
# ActiveSupport::Deprecation.allow ['something broke'], if: Rails.env.production? do
-
# ActiveSupport::Deprecation.warn('something broke!')
-
# end
-
# # => ActiveSupport::DeprecationException for dev/test, nil for production
-
24
def allow(allowed_warnings = :all, if: true, &block)
-
conditional = binding.local_variable_get(:if)
-
conditional = conditional.call if conditional.respond_to?(:call)
-
if conditional
-
@explicitly_allowed_warnings.bind(allowed_warnings, &block)
-
else
-
yield
-
end
-
end
-
-
24
def silenced
-
@silenced || @silenced_thread.value
-
end
-
-
24
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
-
caller_backtrace ||= caller_locations(2)
-
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
-
warn(msg, caller_backtrace)
-
end
-
end
-
-
24
private
-
# Outputs a deprecation warning message
-
#
-
# deprecated_method_warning(:method_name)
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
-
# deprecated_method_warning(:method_name, :another_method)
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
-
# deprecated_method_warning(:method_name, "Optional message")
-
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
-
24
def deprecated_method_warning(method_name, message = nil)
-
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
-
case message
-
when Symbol then "#{warning} (use #{message} instead)"
-
when String then "#{warning} (#{message})"
-
else warning
-
end
-
end
-
-
24
def deprecation_message(callstack, message = nil)
-
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
-
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
-
end
-
-
24
def deprecation_caller_message(callstack)
-
file, line, method = extract_callstack(callstack)
-
if file
-
if line && method
-
"(called from #{method} at #{file}:#{line})"
-
else
-
"(called from #{file}:#{line})"
-
end
-
end
-
end
-
-
24
def extract_callstack(callstack)
-
return _extract_callstack(callstack) if callstack.first.is_a? String
-
-
offending_line = callstack.find { |frame|
-
frame.absolute_path && !ignored_callstack(frame.absolute_path)
-
} || callstack.first
-
-
[offending_line.path, offending_line.lineno, offending_line.label]
-
end
-
-
24
def _extract_callstack(callstack)
-
warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
-
offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
-
-
if offending_line
-
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
-
md.captures
-
else
-
offending_line
-
end
-
end
-
end
-
-
24
RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/"
-
-
24
def ignored_callstack(path)
-
path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "weakref"
-
-
23
module ActiveSupport
-
# This module provides an internal implementation to track descendants
-
# which is faster than iterating through ObjectSpace.
-
23
module DescendantsTracker
-
23
@@direct_descendants = {}
-
-
23
class << self
-
23
def direct_descendants(klass)
-
descendants = @@direct_descendants[klass]
-
descendants ? descendants.to_a : []
-
end
-
23
alias_method :subclasses, :direct_descendants
-
-
23
def descendants(klass)
-
211
arr = []
-
211
accumulate_descendants(klass, arr)
-
211
arr
-
end
-
-
23
def clear
-
if defined? ActiveSupport::Dependencies
-
@@direct_descendants.each do |klass, descendants|
-
if Dependencies.autoloaded?(klass)
-
@@direct_descendants.delete(klass)
-
else
-
descendants.reject! { |v| Dependencies.autoloaded?(v) }
-
end
-
end
-
else
-
@@direct_descendants.clear
-
end
-
end
-
-
# This is the only method that is not thread safe, but is only ever called
-
# during the eager loading phase.
-
23
def store_inherited(klass, descendant)
-
334
(@@direct_descendants[klass] ||= DescendantsArray.new) << descendant
-
end
-
-
23
private
-
23
def accumulate_descendants(klass, acc)
-
211
if direct_descendants = @@direct_descendants[klass]
-
direct_descendants.each do |direct_descendant|
-
acc << direct_descendant
-
accumulate_descendants(direct_descendant, acc)
-
end
-
end
-
end
-
end
-
-
23
def inherited(base)
-
334
DescendantsTracker.store_inherited(self, base)
-
334
super
-
end
-
-
23
def direct_descendants
-
DescendantsTracker.direct_descendants(self)
-
end
-
23
alias_method :subclasses, :direct_descendants
-
-
23
def descendants
-
DescendantsTracker.descendants(self)
-
end
-
-
# DescendantsArray is an array that contains weak references to classes.
-
23
class DescendantsArray # :nodoc:
-
23
include Enumerable
-
-
23
def initialize
-
54
@refs = []
-
end
-
-
23
def initialize_copy(orig)
-
@refs = @refs.dup
-
end
-
-
23
def <<(klass)
-
334
@refs << WeakRef.new(klass)
-
end
-
-
23
def each
-
@refs.reject! do |ref|
-
yield ref.__getobj__
-
false
-
rescue WeakRef::RefError
-
true
-
end
-
self
-
end
-
-
23
def refs_size
-
@refs.size
-
end
-
-
23
def cleanup!
-
@refs.delete_if { |ref| !ref.weakref_alive? }
-
end
-
-
23
def reject!
-
@refs.reject! do |ref|
-
yield ref.__getobj__
-
rescue WeakRef::RefError
-
true
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
class Digest #:nodoc:
-
class <<self
-
def hash_digest_class
-
@hash_digest_class ||= ::Digest::MD5
-
end
-
-
def hash_digest_class=(klass)
-
raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
-
@hash_digest_class = klass
-
end
-
-
def hexdigest(arg)
-
hash_digest_class.hexdigest(arg)[0...32]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/array/conversions"
-
23
require "active_support/core_ext/module/delegation"
-
23
require "active_support/core_ext/object/acts_like"
-
23
require "active_support/core_ext/string/filters"
-
-
23
module ActiveSupport
-
# Provides accurate date and time measurements using Date#advance and
-
# Time#advance, respectively. It mainly supports the methods on Numeric.
-
#
-
# 1.month.ago # equivalent to Time.now.advance(months: -1)
-
23
class Duration
-
23
class Scalar < Numeric #:nodoc:
-
23
attr_reader :value
-
23
delegate :to_i, :to_f, :to_s, to: :value
-
-
23
def initialize(value)
-
@value = value
-
end
-
-
23
def coerce(other)
-
[Scalar.new(other), self]
-
end
-
-
23
def -@
-
Scalar.new(-value)
-
end
-
-
23
def <=>(other)
-
if Scalar === other || Duration === other
-
value <=> other.value
-
elsif Numeric === other
-
value <=> other
-
else
-
nil
-
end
-
end
-
-
23
def +(other)
-
if Duration === other
-
seconds = value + other.parts.fetch(:seconds, 0)
-
new_parts = other.parts.merge(seconds: seconds)
-
new_value = value + other.value
-
-
Duration.new(new_value, new_parts)
-
else
-
calculate(:+, other)
-
end
-
end
-
-
23
def -(other)
-
if Duration === other
-
seconds = value - other.parts.fetch(:seconds, 0)
-
new_parts = other.parts.transform_values(&:-@)
-
new_parts = new_parts.merge(seconds: seconds)
-
new_value = value - other.value
-
-
Duration.new(new_value, new_parts)
-
else
-
calculate(:-, other)
-
end
-
end
-
-
23
def *(other)
-
if Duration === other
-
new_parts = other.parts.transform_values { |other_value| value * other_value }
-
new_value = value * other.value
-
-
Duration.new(new_value, new_parts)
-
else
-
calculate(:*, other)
-
end
-
end
-
-
23
def /(other)
-
if Duration === other
-
value / other.value
-
else
-
calculate(:/, other)
-
end
-
end
-
-
23
def %(other)
-
if Duration === other
-
Duration.build(value % other.value)
-
else
-
calculate(:%, other)
-
end
-
end
-
-
23
private
-
23
def calculate(op, other)
-
if Scalar === other
-
Scalar.new(value.public_send(op, other.value))
-
elsif Numeric === other
-
Scalar.new(value.public_send(op, other))
-
else
-
raise_type_error(other)
-
end
-
end
-
-
23
def raise_type_error(other)
-
raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
-
end
-
end
-
-
23
SECONDS_PER_MINUTE = 60
-
23
SECONDS_PER_HOUR = 3600
-
23
SECONDS_PER_DAY = 86400
-
23
SECONDS_PER_WEEK = 604800
-
23
SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
-
23
SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
-
-
23
PARTS_IN_SECONDS = {
-
seconds: 1,
-
minutes: SECONDS_PER_MINUTE,
-
hours: SECONDS_PER_HOUR,
-
days: SECONDS_PER_DAY,
-
weeks: SECONDS_PER_WEEK,
-
months: SECONDS_PER_MONTH,
-
years: SECONDS_PER_YEAR
-
}.freeze
-
-
23
PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
-
-
23
attr_accessor :value, :parts
-
-
23
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
-
23
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
-
-
23
class << self
-
# Creates a new Duration from string formatted according to ISO 8601 Duration.
-
#
-
# See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
-
# This method allows negative parts to be present in pattern.
-
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
-
23
def parse(iso8601duration)
-
parts = ISO8601Parser.new(iso8601duration).parse!
-
new(calculate_total_seconds(parts), parts)
-
end
-
-
23
def ===(other) #:nodoc:
-
other.is_a?(Duration)
-
rescue ::NoMethodError
-
false
-
end
-
-
23
def seconds(value) #:nodoc:
-
new(value, seconds: value)
-
end
-
-
23
def minutes(value) #:nodoc:
-
new(value * SECONDS_PER_MINUTE, minutes: value)
-
end
-
-
23
def hours(value) #:nodoc:
-
new(value * SECONDS_PER_HOUR, hours: value)
-
end
-
-
23
def days(value) #:nodoc:
-
new(value * SECONDS_PER_DAY, days: value)
-
end
-
-
23
def weeks(value) #:nodoc:
-
new(value * SECONDS_PER_WEEK, weeks: value)
-
end
-
-
23
def months(value) #:nodoc:
-
new(value * SECONDS_PER_MONTH, months: value)
-
end
-
-
23
def years(value) #:nodoc:
-
new(value * SECONDS_PER_YEAR, years: value)
-
end
-
-
# Creates a new Duration from a seconds value that is converted
-
# to the individual parts:
-
#
-
# ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
-
# ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
-
#
-
23
def build(value)
-
unless value.is_a?(::Numeric)
-
raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
-
end
-
-
parts = {}
-
remainder = value.round(9)
-
-
PARTS.each do |part|
-
unless part == :seconds
-
part_in_seconds = PARTS_IN_SECONDS[part]
-
parts[part] = remainder.div(part_in_seconds)
-
remainder %= part_in_seconds
-
end
-
end unless value == 0
-
-
parts[:seconds] = remainder
-
-
new(value, parts)
-
end
-
-
23
private
-
23
def calculate_total_seconds(parts)
-
parts.inject(0) do |total, (part, value)|
-
total + value * PARTS_IN_SECONDS[part]
-
end
-
end
-
end
-
-
23
def initialize(value, parts) #:nodoc:
-
@value, @parts = value, parts
-
@parts.reject! { |k, v| v.zero? } unless value == 0
-
end
-
-
23
def coerce(other) #:nodoc:
-
case other
-
when Scalar
-
[other, self]
-
when Duration
-
[Scalar.new(other.value), self]
-
else
-
[Scalar.new(other), self]
-
end
-
end
-
-
# Compares one Duration with another or a Numeric to this Duration.
-
# Numeric values are treated as seconds.
-
23
def <=>(other)
-
if Duration === other
-
value <=> other.value
-
elsif Numeric === other
-
value <=> other
-
end
-
end
-
-
# Adds another Duration or a Numeric to this Duration. Numeric values
-
# are treated as seconds.
-
23
def +(other)
-
if Duration === other
-
parts = @parts.merge(other.parts) do |_key, value, other_value|
-
value + other_value
-
end
-
Duration.new(value + other.value, parts)
-
else
-
seconds = @parts.fetch(:seconds, 0) + other
-
Duration.new(value + other, @parts.merge(seconds: seconds))
-
end
-
end
-
-
# Subtracts another Duration or a Numeric from this Duration. Numeric
-
# values are treated as seconds.
-
23
def -(other)
-
self + (-other)
-
end
-
-
# Multiplies this Duration by a Numeric and returns a new Duration.
-
23
def *(other)
-
if Scalar === other || Duration === other
-
Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
-
elsif Numeric === other
-
Duration.new(value * other, parts.transform_values { |number| number * other })
-
else
-
raise_type_error(other)
-
end
-
end
-
-
# Divides this Duration by a Numeric and returns a new Duration.
-
23
def /(other)
-
if Scalar === other
-
Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
-
elsif Duration === other
-
value / other.value
-
elsif Numeric === other
-
Duration.new(value / other, parts.transform_values { |number| number / other })
-
else
-
raise_type_error(other)
-
end
-
end
-
-
# Returns the modulo of this Duration by another Duration or Numeric.
-
# Numeric values are treated as seconds.
-
23
def %(other)
-
if Duration === other || Scalar === other
-
Duration.build(value % other.value)
-
elsif Numeric === other
-
Duration.build(value % other)
-
else
-
raise_type_error(other)
-
end
-
end
-
-
23
def -@ #:nodoc:
-
Duration.new(-value, parts.transform_values(&:-@))
-
end
-
-
23
def +@ #:nodoc:
-
self
-
end
-
-
23
def is_a?(klass) #:nodoc:
-
Duration == klass || value.is_a?(klass)
-
end
-
23
alias :kind_of? :is_a?
-
-
23
def instance_of?(klass) # :nodoc:
-
Duration == klass || value.instance_of?(klass)
-
end
-
-
# Returns +true+ if +other+ is also a Duration instance with the
-
# same +value+, or if <tt>other == value</tt>.
-
23
def ==(other)
-
if Duration === other
-
other.value == value
-
else
-
other == value
-
end
-
end
-
-
# Returns the amount of seconds a duration covers as a string.
-
# For more information check to_i method.
-
#
-
# 1.day.to_s # => "86400"
-
23
def to_s
-
@value.to_s
-
end
-
-
# Returns the number of seconds that this Duration represents.
-
#
-
# 1.minute.to_i # => 60
-
# 1.hour.to_i # => 3600
-
# 1.day.to_i # => 86400
-
#
-
# Note that this conversion makes some assumptions about the
-
# duration of some periods, e.g. months are always 1/12 of year
-
# and years are 365.2425 days:
-
#
-
# # equivalent to (1.year / 12).to_i
-
# 1.month.to_i # => 2629746
-
#
-
# # equivalent to 365.2425.days.to_i
-
# 1.year.to_i # => 31556952
-
#
-
# In such cases, Ruby's core
-
# Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
-
# Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
-
# date and time arithmetic.
-
23
def to_i
-
@value.to_i
-
end
-
-
# Returns +true+ if +other+ is also a Duration instance, which has the
-
# same parts as this one.
-
23
def eql?(other)
-
Duration === other && other.value.eql?(value)
-
end
-
-
23
def hash
-
@value.hash
-
end
-
-
# Calculates a new Time or Date that is as far in the future
-
# as this Duration represents.
-
23
def since(time = ::Time.current)
-
sum(1, time)
-
end
-
23
alias :from_now :since
-
23
alias :after :since
-
-
# Calculates a new Time or Date that is as far in the past
-
# as this Duration represents.
-
23
def ago(time = ::Time.current)
-
sum(-1, time)
-
end
-
23
alias :until :ago
-
23
alias :before :ago
-
-
23
def inspect #:nodoc:
-
return "#{value} seconds" if parts.empty?
-
-
parts.
-
sort_by { |unit, _ | PARTS.index(unit) }.
-
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
-
to_sentence(locale: ::I18n.default_locale)
-
end
-
-
23
def as_json(options = nil) #:nodoc:
-
to_i
-
end
-
-
23
def init_with(coder) #:nodoc:
-
initialize(coder["value"], coder["parts"])
-
end
-
-
23
def encode_with(coder) #:nodoc:
-
coder.map = { "value" => @value, "parts" => @parts }
-
end
-
-
# Build ISO 8601 Duration string for this duration.
-
# The +precision+ parameter can be used to limit seconds' precision of duration.
-
23
def iso8601(precision: nil)
-
ISO8601Serializer.new(self, precision: precision).serialize
-
end
-
-
23
private
-
23
def sum(sign, time = ::Time.current)
-
unless time.acts_like?(:time) || time.acts_like?(:date)
-
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
-
end
-
-
if parts.empty?
-
time.since(sign * value)
-
else
-
parts.inject(time) do |t, (type, number)|
-
if type == :seconds
-
t.since(sign * number)
-
elsif type == :minutes
-
t.since(sign * number * 60)
-
elsif type == :hours
-
t.since(sign * number * 3600)
-
else
-
t.advance(type => sign * number)
-
end
-
end
-
end
-
end
-
-
23
def respond_to_missing?(method, _)
-
value.respond_to?(method)
-
end
-
-
23
def method_missing(method, *args, &block)
-
value.public_send(method, *args, &block)
-
end
-
-
23
def raise_type_error(other)
-
raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "strscan"
-
-
module ActiveSupport
-
class Duration
-
# Parses a string formatted according to ISO 8601 Duration into the hash.
-
#
-
# See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
-
#
-
# This parser allows negative parts to be present in pattern.
-
class ISO8601Parser # :nodoc:
-
class ParsingError < ::ArgumentError; end
-
-
PERIOD_OR_COMMA = /\.|,/
-
PERIOD = "."
-
COMMA = ","
-
-
SIGN_MARKER = /\A\-|\+|/
-
DATE_MARKER = /P/
-
TIME_MARKER = /T/
-
DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
-
TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
-
-
DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days }
-
TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds }
-
-
DATE_COMPONENTS = [:years, :months, :days]
-
TIME_COMPONENTS = [:hours, :minutes, :seconds]
-
-
attr_reader :parts, :scanner
-
attr_accessor :mode, :sign
-
-
def initialize(string)
-
@scanner = StringScanner.new(string)
-
@parts = {}
-
@mode = :start
-
@sign = 1
-
end
-
-
def parse!
-
while !finished?
-
case mode
-
when :start
-
if scan(SIGN_MARKER)
-
self.sign = (scanner.matched == "-") ? -1 : 1
-
self.mode = :sign
-
else
-
raise_parsing_error
-
end
-
-
when :sign
-
if scan(DATE_MARKER)
-
self.mode = :date
-
else
-
raise_parsing_error
-
end
-
-
when :date
-
if scan(TIME_MARKER)
-
self.mode = :time
-
elsif scan(DATE_COMPONENT)
-
parts[DATE_TO_PART[scanner[2]]] = number * sign
-
else
-
raise_parsing_error
-
end
-
-
when :time
-
if scan(TIME_COMPONENT)
-
parts[TIME_TO_PART[scanner[2]]] = number * sign
-
else
-
raise_parsing_error
-
end
-
-
end
-
end
-
-
validate!
-
parts
-
end
-
-
private
-
def finished?
-
scanner.eos?
-
end
-
-
# Parses number which can be a float with either comma or period.
-
def number
-
PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
-
end
-
-
def scan(pattern)
-
scanner.scan(pattern)
-
end
-
-
def raise_parsing_error(reason = nil)
-
raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
-
end
-
-
# Checks for various semantic errors as stated in ISO 8601 standard.
-
def validate!
-
raise_parsing_error("is empty duration") if parts.empty?
-
-
# Mixing any of Y, M, D with W is invalid.
-
if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
-
raise_parsing_error("mixing weeks with other date parts not allowed")
-
end
-
-
# Specifying an empty T part is invalid.
-
if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
-
raise_parsing_error("time part marker is present but time part is empty")
-
end
-
-
fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
-
unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
-
raise_parsing_error "(only last part can be fractional)"
-
end
-
-
true
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/object/blank"
-
-
module ActiveSupport
-
class Duration
-
# Serializes duration to string according to ISO 8601 Duration format.
-
class ISO8601Serializer # :nodoc:
-
DATE_COMPONENTS = %i(years months days)
-
-
def initialize(duration, precision: nil)
-
@duration = duration
-
@precision = precision
-
end
-
-
# Builds and returns output string.
-
def serialize
-
parts, sign = normalize
-
return "PT0S" if parts.empty?
-
-
output = +"P"
-
output << "#{parts[:years]}Y" if parts.key?(:years)
-
output << "#{parts[:months]}M" if parts.key?(:months)
-
output << "#{parts[:days]}D" if parts.key?(:days)
-
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
-
time = +""
-
time << "#{parts[:hours]}H" if parts.key?(:hours)
-
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
-
if parts.key?(:seconds)
-
time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
-
end
-
output << "T#{time}" unless time.empty?
-
"#{sign}#{output}"
-
end
-
-
private
-
# Return pair of duration's parts and whole duration sign.
-
# Parts are summarized (as they can become repetitive due to addition, etc).
-
# Zero parts are removed as not significant.
-
# If all parts are negative it will negate all of them and return minus as a sign.
-
def normalize
-
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
-
p[k] += v unless v.zero?
-
end
-
-
# Convert weeks to days and remove weeks if mixed with date parts
-
if week_mixed_with_date?(parts)
-
parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
-
end
-
-
# If all parts are negative - let's make a negative duration
-
sign = ""
-
if parts.values.all? { |v| v < 0 }
-
sign = "-"
-
parts.transform_values!(&:-@)
-
end
-
[parts, sign]
-
end
-
-
def week_mixed_with_date?(parts)
-
parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "yaml"
-
2
require "active_support/encrypted_file"
-
2
require "active_support/ordered_options"
-
2
require "active_support/core_ext/object/inclusion"
-
2
require "active_support/core_ext/module/delegation"
-
-
2
module ActiveSupport
-
2
class EncryptedConfiguration < EncryptedFile
-
2
delegate :[], :fetch, to: :config
-
2
delegate_missing_to :options
-
-
2
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
-
super content_path: config_path, key_path: key_path,
-
env_key: env_key, raise_if_missing_key: raise_if_missing_key
-
end
-
-
# Allow a config to be started without a file present
-
2
def read
-
super
-
rescue ActiveSupport::EncryptedFile::MissingContentError
-
""
-
end
-
-
2
def write(contents)
-
deserialize(contents)
-
-
super
-
end
-
-
2
def config
-
@config ||= deserialize(read).deep_symbolize_keys
-
end
-
-
2
private
-
2
def options
-
@options ||= ActiveSupport::InheritableOptions.new(config)
-
end
-
-
2
def deserialize(config)
-
YAML.load(config).presence || {}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "pathname"
-
2
require "tmpdir"
-
2
require "active_support/message_encryptor"
-
-
2
module ActiveSupport
-
2
class EncryptedFile
-
2
class MissingContentError < RuntimeError
-
2
def initialize(content_path)
-
super "Missing encrypted content file in #{content_path}."
-
end
-
end
-
-
2
class MissingKeyError < RuntimeError
-
2
def initialize(key_path:, env_key:)
-
super \
-
"Missing encryption key to decrypt file with. " +
-
"Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
-
end
-
end
-
-
2
CIPHER = "aes-128-gcm"
-
-
2
def self.generate_key
-
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
-
end
-
-
-
2
attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
-
-
2
def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
-
@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
-
@key_path = Pathname.new(key_path)
-
@env_key, @raise_if_missing_key = env_key, raise_if_missing_key
-
end
-
-
2
def key
-
read_env_key || read_key_file || handle_missing_key
-
end
-
-
2
def read
-
if !key.nil? && content_path.exist?
-
decrypt content_path.binread
-
else
-
raise MissingContentError, content_path
-
end
-
end
-
-
2
def write(contents)
-
IO.binwrite "#{content_path}.tmp", encrypt(contents)
-
FileUtils.mv "#{content_path}.tmp", content_path
-
end
-
-
2
def change(&block)
-
writing read, &block
-
end
-
-
-
2
private
-
2
def writing(contents)
-
tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}"
-
tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
-
tmp_path.binwrite contents
-
-
yield tmp_path
-
-
updated_contents = tmp_path.binread
-
-
write(updated_contents) if updated_contents != contents
-
ensure
-
FileUtils.rm(tmp_path) if tmp_path&.exist?
-
end
-
-
-
2
def encrypt(contents)
-
encryptor.encrypt_and_sign contents
-
end
-
-
2
def decrypt(contents)
-
encryptor.decrypt_and_verify contents
-
end
-
-
2
def encryptor
-
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
-
end
-
-
-
2
def read_env_key
-
ENV[env_key]
-
end
-
-
2
def read_key_file
-
key_path.binread.strip if key_path.exist?
-
end
-
-
2
def handle_missing_key
-
raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/string_inquirer"
-
-
1
module ActiveSupport
-
1
class EnvironmentInquirer < StringInquirer #:nodoc:
-
1
DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
-
1
def initialize(env)
-
super(env)
-
-
DEFAULT_ENVIRONMENTS.each do |default|
-
instance_variable_set :"@#{default}", env == default
-
end
-
end
-
-
1
DEFAULT_ENVIRONMENTS.each do |env|
-
3
class_eval "def #{env}?; @#{env}; end"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "set"
-
require "pathname"
-
require "concurrent/atomic/atomic_boolean"
-
require "listen"
-
-
module ActiveSupport
-
# Allows you to "listen" to changes in a file system.
-
# The evented file updater does not hit disk when checking for updates
-
# instead it uses platform specific file system events to trigger a change
-
# in state.
-
#
-
# The file checker takes an array of files to watch or a hash specifying directories
-
# and file extensions to watch. It also takes a block that is called when
-
# EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
-
# is run and there have been changes to the file system.
-
#
-
# Note: Forking will cause the first call to `updated?` to return `true`.
-
#
-
# Example:
-
#
-
# checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
-
# checker.updated?
-
# # => false
-
# checker.execute_if_updated
-
# # => nil
-
#
-
# FileUtils.touch("/tmp/foo")
-
#
-
# checker.updated?
-
# # => true
-
# checker.execute_if_updated
-
# # => "changed"
-
#
-
class EventedFileUpdateChecker #:nodoc: all
-
def initialize(files, dirs = {}, &block)
-
unless block
-
raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
-
end
-
-
@ph = PathHelper.new
-
@files = files.map { |f| @ph.xpath(f) }.to_set
-
-
@dirs = {}
-
dirs.each do |dir, exts|
-
@dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
-
end
-
-
@block = block
-
@updated = Concurrent::AtomicBoolean.new(false)
-
@lcsp = @ph.longest_common_subpath(@dirs.keys)
-
@pid = Process.pid
-
@boot_mutex = Mutex.new
-
-
dtw = directories_to_watch
-
@dtw, @missing = dtw.partition(&:exist?)
-
-
boot!
-
end
-
-
def updated?
-
@boot_mutex.synchronize do
-
if @pid != Process.pid
-
boot!
-
@pid = Process.pid
-
@updated.make_true
-
end
-
end
-
-
if @missing.any?(&:exist?)
-
@boot_mutex.synchronize do
-
appeared, @missing = @missing.partition(&:exist?)
-
shutdown!
-
-
@dtw += appeared
-
boot!
-
-
@updated.make_true
-
end
-
end
-
-
@updated.true?
-
end
-
-
def execute
-
@updated.make_false
-
@block.call
-
end
-
-
def execute_if_updated
-
if updated?
-
yield if block_given?
-
execute
-
true
-
end
-
end
-
-
private
-
def boot!
-
normalize_dirs!
-
-
Listen.to(*@dtw, &method(:changed)).start if @dtw.any?
-
end
-
-
def shutdown!
-
Listen.stop
-
end
-
-
def normalize_dirs!
-
@dirs.transform_keys! do |dir|
-
dir.exist? ? dir.realpath : dir
-
end
-
end
-
-
def changed(modified, added, removed)
-
unless updated?
-
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
-
end
-
end
-
-
def watching?(file)
-
file = @ph.xpath(file)
-
-
if @files.member?(file)
-
true
-
elsif file.directory?
-
false
-
else
-
ext = @ph.normalize_extension(file.extname)
-
-
file.dirname.ascend do |dir|
-
matching = @dirs[dir]
-
-
if matching && (matching.empty? || matching.include?(ext))
-
break true
-
elsif dir == @lcsp || dir.root?
-
break false
-
end
-
end
-
end
-
end
-
-
def directories_to_watch
-
dtw = @files.map(&:dirname) + @dirs.keys
-
dtw.compact!
-
dtw.uniq!
-
-
normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
-
dtw = dtw.reject do |path|
-
normalized_gem_paths.any? { |gem_path| path.to_path.start_with?(gem_path) }
-
end
-
-
@ph.filter_out_descendants(dtw)
-
end
-
-
class PathHelper
-
def xpath(path)
-
Pathname.new(path).expand_path
-
end
-
-
def normalize_extension(ext)
-
ext.to_s.delete_prefix(".")
-
end
-
-
# Given a collection of Pathname objects returns the longest subpath
-
# common to all of them, or +nil+ if there is none.
-
def longest_common_subpath(paths)
-
return if paths.empty?
-
-
lcsp = Pathname.new(paths[0])
-
-
paths[1..-1].each do |path|
-
until ascendant_of?(lcsp, path)
-
if lcsp.root?
-
# If we get here a root directory is not an ascendant of path.
-
# This may happen if there are paths in different drives on
-
# Windows.
-
return
-
else
-
lcsp = lcsp.parent
-
end
-
end
-
end
-
-
lcsp
-
end
-
-
# Filters out directories which are descendants of others in the collection (stable).
-
def filter_out_descendants(dirs)
-
return dirs if dirs.length < 2
-
-
dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
-
descendants = []
-
-
until dirs_sorted_by_nparts.empty?
-
dir = dirs_sorted_by_nparts.shift
-
-
dirs_sorted_by_nparts.reject! do |possible_descendant|
-
ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
-
end
-
end
-
-
# Array#- preserves order.
-
dirs - descendants
-
end
-
-
private
-
def ascendant_of?(base, other)
-
base != other && other.ascend do |ascendant|
-
break true if base == ascendant
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/callbacks"
-
require "concurrent/hash"
-
-
module ActiveSupport
-
class ExecutionWrapper
-
include ActiveSupport::Callbacks
-
-
Null = Object.new # :nodoc:
-
def Null.complete! # :nodoc:
-
end
-
-
define_callbacks :run
-
define_callbacks :complete
-
-
def self.to_run(*args, &block)
-
set_callback(:run, *args, &block)
-
end
-
-
def self.to_complete(*args, &block)
-
set_callback(:complete, *args, &block)
-
end
-
-
RunHook = Struct.new(:hook) do # :nodoc:
-
def before(target)
-
hook_state = target.send(:hook_state)
-
hook_state[hook] = hook.run
-
end
-
end
-
-
CompleteHook = Struct.new(:hook) do # :nodoc:
-
def before(target)
-
hook_state = target.send(:hook_state)
-
if hook_state.key?(hook)
-
hook.complete hook_state[hook]
-
end
-
end
-
alias after before
-
end
-
-
# Register an object to be invoked during both the +run+ and
-
# +complete+ steps.
-
#
-
# +hook.complete+ will be passed the value returned from +hook.run+,
-
# and will only be invoked if +run+ has previously been called.
-
# (Mostly, this means it won't be invoked if an exception occurs in
-
# a preceding +to_run+ block; all ordinary +to_complete+ blocks are
-
# invoked in that situation.)
-
def self.register_hook(hook, outer: false)
-
if outer
-
to_run RunHook.new(hook), prepend: true
-
to_complete :after, CompleteHook.new(hook)
-
else
-
to_run RunHook.new(hook)
-
to_complete CompleteHook.new(hook)
-
end
-
end
-
-
# Run this execution.
-
#
-
# Returns an instance, whose +complete!+ method *must* be invoked
-
# after the work has been performed.
-
#
-
# Where possible, prefer +wrap+.
-
def self.run!
-
if active?
-
Null
-
else
-
new.tap do |instance|
-
success = nil
-
begin
-
instance.run!
-
success = true
-
ensure
-
instance.complete! unless success
-
end
-
end
-
end
-
end
-
-
# Perform the work in the supplied block as an execution.
-
def self.wrap
-
return yield if active?
-
-
instance = run!
-
begin
-
yield
-
ensure
-
instance.complete!
-
end
-
end
-
-
class << self # :nodoc:
-
attr_accessor :active
-
end
-
-
def self.inherited(other) # :nodoc:
-
super
-
other.active = Concurrent::Hash.new
-
end
-
-
self.active = Concurrent::Hash.new
-
-
def self.active? # :nodoc:
-
@active[Thread.current]
-
end
-
-
def run! # :nodoc:
-
self.class.active[Thread.current] = true
-
run_callbacks(:run)
-
end
-
-
# Complete this in-flight execution. This method *must* be called
-
# exactly once on the result of any call to +run!+.
-
#
-
# Where possible, prefer +wrap+.
-
def complete!
-
run_callbacks(:complete)
-
ensure
-
self.class.active.delete Thread.current
-
end
-
-
private
-
def hook_state
-
@_hook_state ||= {}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/execution_wrapper"
-
-
module ActiveSupport
-
class Executor < ExecutionWrapper
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/time/calculations"
-
-
module ActiveSupport
-
# FileUpdateChecker specifies the API used by Rails to watch files
-
# and control reloading. The API depends on four methods:
-
#
-
# * +initialize+ which expects two parameters and one block as
-
# described below.
-
#
-
# * +updated?+ which returns a boolean if there were updates in
-
# the filesystem or not.
-
#
-
# * +execute+ which executes the given block on initialization
-
# and updates the latest watched files and timestamp.
-
#
-
# * +execute_if_updated+ which just executes the block if it was updated.
-
#
-
# After initialization, a call to +execute_if_updated+ must execute
-
# the block only if there was really a change in the filesystem.
-
#
-
# This class is used by Rails to reload the I18n framework whenever
-
# they are changed upon a new request.
-
#
-
# i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
-
# I18n.reload!
-
# end
-
#
-
# ActiveSupport::Reloader.to_prepare do
-
# i18n_reloader.execute_if_updated
-
# end
-
class FileUpdateChecker
-
# It accepts two parameters on initialization. The first is an array
-
# of files and the second is an optional hash of directories. The hash must
-
# have directories as keys and the value is an array of extensions to be
-
# watched under that directory.
-
#
-
# This method must also receive a block that will be called once a path
-
# changes. The array of files and list of directories cannot be changed
-
# after FileUpdateChecker has been initialized.
-
def initialize(files, dirs = {}, &block)
-
unless block
-
raise ArgumentError, "A block is required to initialize a FileUpdateChecker"
-
end
-
-
@files = files.freeze
-
@glob = compile_glob(dirs)
-
@block = block
-
-
@watched = nil
-
@updated_at = nil
-
-
@last_watched = watched
-
@last_update_at = updated_at(@last_watched)
-
end
-
-
# Check if any of the entries were updated. If so, the watched and/or
-
# updated_at values are cached until the block is executed via +execute+
-
# or +execute_if_updated+.
-
def updated?
-
current_watched = watched
-
if @last_watched.size != current_watched.size
-
@watched = current_watched
-
true
-
else
-
current_updated_at = updated_at(current_watched)
-
if @last_update_at < current_updated_at
-
@watched = current_watched
-
@updated_at = current_updated_at
-
true
-
else
-
false
-
end
-
end
-
end
-
-
# Executes the given block and updates the latest watched files and
-
# timestamp.
-
def execute
-
@last_watched = watched
-
@last_update_at = updated_at(@last_watched)
-
@block.call
-
ensure
-
@watched = nil
-
@updated_at = nil
-
end
-
-
# Execute the block given if updated.
-
def execute_if_updated
-
if updated?
-
yield if block_given?
-
execute
-
true
-
else
-
false
-
end
-
end
-
-
private
-
def watched
-
@watched || begin
-
all = @files.select { |f| File.exist?(f) }
-
all.concat(Dir[@glob]) if @glob
-
all
-
end
-
end
-
-
def updated_at(paths)
-
@updated_at || max_mtime(paths) || Time.at(0)
-
end
-
-
# This method returns the maximum mtime of the files in +paths+, or +nil+
-
# if the array is empty.
-
#
-
# Files with a mtime in the future are ignored. Such abnormal situation
-
# can happen for example if the user changes the clock by hand. It is
-
# healthy to consider this edge case because with mtimes in the future
-
# reloading is not triggered.
-
def max_mtime(paths)
-
time_now = Time.now
-
max_mtime = nil
-
-
# Time comparisons are performed with #compare_without_coercion because
-
# AS redefines these operators in a way that is much slower and does not
-
# bring any benefit in this particular code.
-
#
-
# Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
-
paths.each do |path|
-
mtime = File.mtime(path)
-
-
next if time_now.compare_without_coercion(mtime) < 0
-
-
if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
-
max_mtime = mtime
-
end
-
end
-
-
max_mtime
-
end
-
-
def compile_glob(hash)
-
hash.freeze # Freeze so changes aren't accidentally pushed
-
return if hash.empty?
-
-
globs = hash.map do |key, value|
-
"#{escape(key)}/**/*#{compile_ext(value)}"
-
end
-
"{#{globs.join(",")}}"
-
end
-
-
def escape(key)
-
key.gsub(",", '\,')
-
end
-
-
def compile_ext(array)
-
array = Array(array)
-
return if array.empty?
-
".{#{array.join(",")}}"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
module ForkTracker # :nodoc:
-
module CoreExt
-
def fork(*)
-
if block_given?
-
super do
-
ForkTracker.check!
-
yield
-
end
-
else
-
unless pid = super
-
ForkTracker.check!
-
end
-
pid
-
end
-
end
-
end
-
-
module CoreExtPrivate
-
include CoreExt
-
private :fork
-
end
-
-
@pid = Process.pid
-
@callbacks = []
-
-
class << self
-
def check!
-
if @pid != Process.pid
-
@callbacks.each(&:call)
-
@pid = Process.pid
-
end
-
end
-
-
def hook!
-
if Process.respond_to?(:fork)
-
::Object.prepend(CoreExtPrivate)
-
::Kernel.prepend(CoreExtPrivate)
-
::Kernel.singleton_class.prepend(CoreExt)
-
::Process.singleton_class.prepend(CoreExt)
-
end
-
end
-
-
def after_fork(&block)
-
@callbacks << block
-
block
-
end
-
-
def unregister(callback)
-
@callbacks.delete(callback)
-
end
-
end
-
end
-
end
-
-
ActiveSupport::ForkTracker.hook!
-
# frozen_string_literal: true
-
-
24
module ActiveSupport
-
# Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
-
24
def self.gem_version
-
Gem::Version.new VERSION::STRING
-
end
-
-
24
module VERSION
-
24
MAJOR = 6
-
24
MINOR = 1
-
24
TINY = 0
-
24
PRE = "alpha"
-
-
24
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
-
end
-
end
-
# frozen_string_literal: true
-
-
require "zlib"
-
require "stringio"
-
-
module ActiveSupport
-
# A convenient wrapper for the zlib standard library that allows
-
# compression/decompression of strings with gzip.
-
#
-
# gzip = ActiveSupport::Gzip.compress('compress me!')
-
# # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00"
-
#
-
# ActiveSupport::Gzip.decompress(gzip)
-
# # => "compress me!"
-
module Gzip
-
class Stream < StringIO
-
def initialize(*)
-
super
-
set_encoding "BINARY"
-
end
-
def close; rewind; end
-
end
-
-
# Decompresses a gzipped string.
-
def self.decompress(source)
-
Zlib::GzipReader.wrap(StringIO.new(source), &:read)
-
end
-
-
# Compresses a string using gzip.
-
def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
-
output = Stream.new
-
gz = Zlib::GzipWriter.new(output, level, strategy)
-
gz.write(source)
-
gz.close
-
output.string
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/hash/keys"
-
1
require "active_support/core_ext/hash/reverse_merge"
-
1
require "active_support/core_ext/hash/except"
-
-
1
module ActiveSupport
-
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
-
# to be the same.
-
#
-
# rgb = ActiveSupport::HashWithIndifferentAccess.new
-
#
-
# rgb[:black] = '#000000'
-
# rgb[:black] # => '#000000'
-
# rgb['black'] # => '#000000'
-
#
-
# rgb['white'] = '#FFFFFF'
-
# rgb[:white] # => '#FFFFFF'
-
# rgb['white'] # => '#FFFFFF'
-
#
-
# Internally symbols are mapped to strings when used as keys in the entire
-
# writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
-
# mapping belongs to the public interface. For example, given:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
-
#
-
# You are guaranteed that the key is returned as a string:
-
#
-
# hash.keys # => ["a"]
-
#
-
# Technically other types of keys are accepted:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
-
# hash[0] = 0
-
# hash # => {"a"=>1, 0=>0}
-
#
-
# but this class is intended for use cases where strings or symbols are the
-
# expected keys and it is convenient to understand both as the same. For
-
# example the +params+ hash in Ruby on Rails.
-
#
-
# Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
-
#
-
# rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
-
#
-
# which may be handy.
-
#
-
# To access this class outside of Rails, require the core extension with:
-
#
-
# require "active_support/core_ext/hash/indifferent_access"
-
#
-
# which will, in turn, require this file.
-
1
class HashWithIndifferentAccess < Hash
-
# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
-
# this class.
-
1
def extractable_options?
-
true
-
end
-
-
1
def with_indifferent_access
-
dup
-
end
-
-
1
def nested_under_indifferent_access
-
self
-
end
-
-
1
def initialize(constructor = {})
-
if constructor.respond_to?(:to_hash)
-
super()
-
update(constructor)
-
-
hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
-
self.default = hash.default if hash.default
-
self.default_proc = hash.default_proc if hash.default_proc
-
else
-
super(constructor)
-
end
-
end
-
-
1
def self.[](*args)
-
new.merge!(Hash[*args])
-
end
-
-
1
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
-
1
alias_method :regular_update, :update unless method_defined?(:regular_update)
-
-
# Assigns a new value to the hash:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash[:key] = 'value'
-
#
-
# This value can be later fetched using either +:key+ or <tt>'key'</tt>.
-
1
def []=(key, value)
-
regular_writer(convert_key(key), convert_value(value, conversion: :assignment))
-
end
-
-
1
alias_method :store, :[]=
-
-
# Updates the receiver in-place, merging in the hashes passed as arguments:
-
#
-
# hash_1 = ActiveSupport::HashWithIndifferentAccess.new
-
# hash_1[:key] = 'value'
-
#
-
# hash_2 = ActiveSupport::HashWithIndifferentAccess.new
-
# hash_2[:key] = 'New Value!'
-
#
-
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash.update({ "a" => 1 }, { "b" => 2 }) # => { "a" => 1, "b" => 2 }
-
#
-
# The arguments can be either an
-
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
-
# In either case the merge respects the semantics of indifferent access.
-
#
-
# If the argument is a regular hash with keys +:key+ and +"key"+ only one
-
# of the values end up in the receiver, but which one is unspecified.
-
#
-
# When given a block, the value for duplicated keys will be determined
-
# by the result of invoking the block with the duplicated key, the value
-
# in the receiver, and the value in +other_hash+. The rules for duplicated
-
# keys follow the semantics of indifferent access:
-
#
-
# hash_1[:key] = 10
-
# hash_2['key'] = 12
-
# hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
-
1
def update(*other_hashes, &block)
-
if other_hashes.size == 1
-
update_with_single_argument(other_hashes.first, block)
-
else
-
other_hashes.each do |other_hash|
-
update_with_single_argument(other_hash, block)
-
end
-
end
-
self
-
end
-
-
1
alias_method :merge!, :update
-
-
# Checks the hash for a key matching the argument passed in:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash['key'] = 'value'
-
# hash.key?(:key) # => true
-
# hash.key?('key') # => true
-
1
def key?(key)
-
super(convert_key(key))
-
end
-
-
1
alias_method :include?, :key?
-
1
alias_method :has_key?, :key?
-
1
alias_method :member?, :key?
-
-
# Same as <tt>Hash#[]</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# counters = ActiveSupport::HashWithIndifferentAccess.new
-
# counters[:foo] = 1
-
#
-
# counters['foo'] # => 1
-
# counters[:foo] # => 1
-
# counters[:zoo] # => nil
-
1
def [](key)
-
super(convert_key(key))
-
end
-
-
# Same as <tt>Hash#assoc</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# counters = ActiveSupport::HashWithIndifferentAccess.new
-
# counters[:foo] = 1
-
#
-
# counters.assoc('foo') # => ["foo", 1]
-
# counters.assoc(:foo) # => ["foo", 1]
-
# counters.assoc(:zoo) # => nil
-
1
def assoc(key)
-
super(convert_key(key))
-
end
-
-
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# counters = ActiveSupport::HashWithIndifferentAccess.new
-
# counters[:foo] = 1
-
#
-
# counters.fetch('foo') # => 1
-
# counters.fetch(:bar, 0) # => 0
-
# counters.fetch(:bar) { |key| 0 } # => 0
-
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
-
1
def fetch(key, *extras)
-
super(convert_key(key), *extras)
-
end
-
-
# Same as <tt>Hash#dig</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# counters = ActiveSupport::HashWithIndifferentAccess.new
-
# counters[:foo] = { bar: 1 }
-
#
-
# counters.dig('foo', 'bar') # => 1
-
# counters.dig(:foo, :bar) # => 1
-
# counters.dig(:zoo) # => nil
-
1
def dig(*args)
-
args[0] = convert_key(args[0]) if args.size > 0
-
super(*args)
-
end
-
-
# Same as <tt>Hash#default</tt> where the key passed as argument can be
-
# either a string or a symbol:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new(1)
-
# hash.default # => 1
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key }
-
# hash.default # => nil
-
# hash.default('foo') # => 'foo'
-
# hash.default(:foo) # => 'foo'
-
1
def default(*args)
-
super(*args.map { |arg| convert_key(arg) })
-
end
-
-
# Returns an array of the values at the specified indices:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash[:a] = 'x'
-
# hash[:b] = 'y'
-
# hash.values_at('a', 'b') # => ["x", "y"]
-
1
def values_at(*keys)
-
super(*keys.map { |key| convert_key(key) })
-
end
-
-
# Returns an array of the values at the specified indices, but also
-
# raises an exception when one of the keys can't be found.
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash[:a] = 'x'
-
# hash[:b] = 'y'
-
# hash.fetch_values('a', 'b') # => ["x", "y"]
-
# hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
-
# hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
-
1
def fetch_values(*indices, &block)
-
super(*indices.map { |key| convert_key(key) }, &block)
-
end
-
-
# Returns a shallow copy of the hash.
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
-
# dup = hash.dup
-
# dup[:a][:c] = 'c'
-
#
-
# hash[:a][:c] # => "c"
-
# dup[:a][:c] # => "c"
-
1
def dup
-
self.class.new(self).tap do |new_hash|
-
set_defaults(new_hash)
-
end
-
end
-
-
# This method has the same semantics of +update+, except it does not
-
# modify the receiver but rather returns a new hash with indifferent
-
# access with the result of the merge.
-
1
def merge(*hashes, &block)
-
dup.update(*hashes, &block)
-
end
-
-
# Like +merge+ but the other way around: Merges the receiver into the
-
# argument and returns a new hash with indifferent access as result:
-
#
-
# hash = ActiveSupport::HashWithIndifferentAccess.new
-
# hash['a'] = nil
-
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
-
1
def reverse_merge(other_hash)
-
super(self.class.new(other_hash))
-
end
-
1
alias_method :with_defaults, :reverse_merge
-
-
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
-
1
def reverse_merge!(other_hash)
-
super(self.class.new(other_hash))
-
end
-
1
alias_method :with_defaults!, :reverse_merge!
-
-
# Replaces the contents of this hash with other_hash.
-
#
-
# h = { "a" => 100, "b" => 200 }
-
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
-
1
def replace(other_hash)
-
super(self.class.new(other_hash))
-
end
-
-
# Removes the specified key from the hash.
-
1
def delete(key)
-
super(convert_key(key))
-
end
-
-
1
def except(*keys)
-
slice(*self.keys - keys.map { |key| convert_key(key) })
-
end
-
1
alias_method :without, :except
-
-
1
def stringify_keys!; self end
-
1
def deep_stringify_keys!; self end
-
1
def stringify_keys; dup end
-
1
def deep_stringify_keys; dup end
-
1
undef :symbolize_keys!
-
1
undef :deep_symbolize_keys!
-
1
def symbolize_keys; to_hash.symbolize_keys! end
-
1
alias_method :to_options, :symbolize_keys
-
1
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
-
1
def to_options!; self end
-
-
1
def select(*args, &block)
-
return to_enum(:select) unless block_given?
-
dup.tap { |hash| hash.select!(*args, &block) }
-
end
-
-
1
def reject(*args, &block)
-
return to_enum(:reject) unless block_given?
-
dup.tap { |hash| hash.reject!(*args, &block) }
-
end
-
-
1
def transform_values(*args, &block)
-
return to_enum(:transform_values) unless block_given?
-
dup.tap { |hash| hash.transform_values!(*args, &block) }
-
end
-
-
1
def transform_keys(*args, &block)
-
return to_enum(:transform_keys) unless block_given?
-
dup.tap { |hash| hash.transform_keys!(*args, &block) }
-
end
-
-
1
def transform_keys!
-
return enum_for(:transform_keys!) { size } unless block_given?
-
keys.each do |key|
-
self[yield(key)] = delete(key)
-
end
-
self
-
end
-
-
1
def slice(*keys)
-
keys.map! { |key| convert_key(key) }
-
self.class.new(super)
-
end
-
-
1
def slice!(*keys)
-
keys.map! { |key| convert_key(key) }
-
super
-
end
-
-
1
def compact
-
dup.tap(&:compact!)
-
end
-
-
# Convert to a regular hash with string keys.
-
1
def to_hash
-
_new_hash = Hash.new
-
set_defaults(_new_hash)
-
-
each do |key, value|
-
_new_hash[key] = convert_value(value, conversion: :to_hash)
-
end
-
_new_hash
-
end
-
-
1
private
-
1
def convert_key(key)
-
key.kind_of?(Symbol) ? key.to_s : key
-
end
-
-
1
def convert_value(value, conversion: nil)
-
if value.is_a? Hash
-
if conversion == :to_hash
-
value.to_hash
-
else
-
value.nested_under_indifferent_access
-
end
-
elsif value.is_a?(Array)
-
if conversion != :assignment || value.frozen?
-
value = value.dup
-
end
-
value.map! { |e| convert_value(e, conversion: conversion) }
-
else
-
value
-
end
-
end
-
-
1
def set_defaults(target)
-
if default_proc
-
target.default_proc = default_proc.dup
-
else
-
target.default = default
-
end
-
end
-
-
1
def update_with_single_argument(other_hash, block)
-
if other_hash.is_a? HashWithIndifferentAccess
-
regular_update(other_hash, &block)
-
else
-
other_hash.to_hash.each_pair do |key, value|
-
if block && key?(key)
-
value = block.call(convert_key(key), self[key], value)
-
end
-
regular_writer(convert_key(key), convert_value(value))
-
end
-
end
-
end
-
end
-
end
-
-
# :stopdoc:
-
-
1
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
# frozen_string_literal: true
-
-
24
require "active_support/core_ext/hash/deep_merge"
-
24
require "active_support/core_ext/hash/except"
-
24
require "active_support/core_ext/hash/slice"
-
24
begin
-
24
require "i18n"
-
rescue LoadError => e
-
$stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
24
require "active_support/lazy_load_hooks"
-
-
24
ActiveSupport.run_load_hooks(:i18n)
-
24
I18n.load_path << File.expand_path("locale/en.yml", __dir__)
-
24
I18n.load_path << File.expand_path("locale/en.rb", __dir__)
-
# frozen_string_literal: true
-
-
require "active_support"
-
require "active_support/core_ext/array/wrap"
-
-
# :enddoc:
-
-
module I18n
-
class Railtie < Rails::Railtie
-
config.i18n = ActiveSupport::OrderedOptions.new
-
config.i18n.railties_load_path = []
-
config.i18n.load_path = []
-
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
-
-
config.eager_load_namespaces << I18n
-
-
# Set the i18n configuration after initialization since a lot of
-
# configuration is still usually done in application initializers.
-
config.after_initialize do |app|
-
I18n::Railtie.initialize_i18n(app)
-
end
-
-
# Trigger i18n config before any eager loading has happened
-
# so it's ready if any classes require it when eager loaded.
-
config.before_eager_load do |app|
-
I18n::Railtie.initialize_i18n(app)
-
end
-
-
@i18n_inited = false
-
-
# Setup i18n configuration.
-
def self.initialize_i18n(app)
-
return if @i18n_inited
-
-
fallbacks = app.config.i18n.delete(:fallbacks)
-
-
# Avoid issues with setting the default_locale by disabling available locales
-
# check while configuring.
-
enforce_available_locales = app.config.i18n.delete(:enforce_available_locales)
-
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
-
I18n.enforce_available_locales = false
-
-
reloadable_paths = []
-
app.config.i18n.each do |setting, value|
-
case setting
-
when :railties_load_path
-
reloadable_paths = value
-
app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
-
when :load_path
-
I18n.load_path += value
-
when :raise_on_missing_translations
-
forward_raise_on_missing_translations_config(app)
-
else
-
I18n.send("#{setting}=", value)
-
end
-
end
-
-
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
-
-
# Restore available locales check so it will take place from now on.
-
I18n.enforce_available_locales = enforce_available_locales
-
-
directories = watched_dirs_with_extensions(reloadable_paths)
-
reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
-
I18n.load_path.keep_if { |p| File.exist?(p) }
-
I18n.load_path |= reloadable_paths.flat_map(&:existent)
-
end
-
-
app.reloaders << reloader
-
app.reloader.to_run do
-
reloader.execute_if_updated { require_unload_lock! }
-
end
-
reloader.execute
-
-
@i18n_inited = true
-
end
-
-
def self.forward_raise_on_missing_translations_config(app)
-
ActiveSupport.on_load(:action_view) do
-
self.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
-
end
-
-
ActiveSupport.on_load(:action_controller) do
-
AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
-
end
-
end
-
-
def self.include_fallbacks_module
-
I18n.backend.class.include(I18n::Backend::Fallbacks)
-
end
-
-
def self.init_fallbacks(fallbacks)
-
include_fallbacks_module
-
-
args = \
-
case fallbacks
-
when ActiveSupport::OrderedOptions
-
[*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
-
when Hash, Array
-
Array.wrap(fallbacks)
-
else # TrueClass
-
[I18n.default_locale]
-
end
-
-
if args.empty? || args.first.is_a?(Hash)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Using I18n fallbacks with an empty `defaults` sets the defaults to
-
include the `default_locale`. This behavior will change in Rails 6.1.
-
If you desire the default locale to be included in the defaults, please
-
explicitly configure it with `config.i18n.fallbacks.defaults =
-
[I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale,
-
{...}]`. If you want to opt-in to the new behavior, use
-
`config.i18n.fallbacks.defaults = [nil, {...}]`.
-
MSG
-
args.unshift I18n.default_locale
-
end
-
-
I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
-
end
-
-
def self.validate_fallbacks(fallbacks)
-
case fallbacks
-
when ActiveSupport::OrderedOptions
-
!fallbacks.empty?
-
when TrueClass, Array, Hash
-
true
-
else
-
raise "Unexpected fallback type #{fallbacks.inspect}"
-
end
-
end
-
-
def self.watched_dirs_with_extensions(paths)
-
paths.each_with_object({}) do |path, result|
-
result[path.absolute_current] = path.extensions
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/inflector/inflections"
-
-
#--
-
# Defines the standard inflection rules. These are the starting point for
-
# new projects and are not considered complete. The current set of inflection
-
# rules is frozen. This means, we do not change them to become more complete.
-
# This is a safety measure to keep existing applications from breaking.
-
#++
-
24
module ActiveSupport
-
24
Inflector.inflections(:en) do |inflect|
-
24
inflect.plural(/$/, "s")
-
24
inflect.plural(/s$/i, "s")
-
24
inflect.plural(/^(ax|test)is$/i, '\1es')
-
24
inflect.plural(/(octop|vir)us$/i, '\1i')
-
24
inflect.plural(/(octop|vir)i$/i, '\1i')
-
24
inflect.plural(/(alias|status)$/i, '\1es')
-
24
inflect.plural(/(bu)s$/i, '\1ses')
-
24
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
-
24
inflect.plural(/([ti])um$/i, '\1a')
-
24
inflect.plural(/([ti])a$/i, '\1a')
-
24
inflect.plural(/sis$/i, "ses")
-
24
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
-
24
inflect.plural(/(hive)$/i, '\1s')
-
24
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
-
24
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
-
24
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
-
24
inflect.plural(/^(m|l)ouse$/i, '\1ice')
-
24
inflect.plural(/^(m|l)ice$/i, '\1ice')
-
24
inflect.plural(/^(ox)$/i, '\1en')
-
24
inflect.plural(/^(oxen)$/i, '\1')
-
24
inflect.plural(/(quiz)$/i, '\1zes')
-
-
24
inflect.singular(/s$/i, "")
-
24
inflect.singular(/(ss)$/i, '\1')
-
24
inflect.singular(/(n)ews$/i, '\1ews')
-
24
inflect.singular(/([ti])a$/i, '\1um')
-
24
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
-
24
inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
-
24
inflect.singular(/([^f])ves$/i, '\1fe')
-
24
inflect.singular(/(hive)s$/i, '\1')
-
24
inflect.singular(/(tive)s$/i, '\1')
-
24
inflect.singular(/([lr])ves$/i, '\1f')
-
24
inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
-
24
inflect.singular(/(s)eries$/i, '\1eries')
-
24
inflect.singular(/(m)ovies$/i, '\1ovie')
-
24
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
-
24
inflect.singular(/^(m|l)ice$/i, '\1ouse')
-
24
inflect.singular(/(bus)(es)?$/i, '\1')
-
24
inflect.singular(/(o)es$/i, '\1')
-
24
inflect.singular(/(shoe)s$/i, '\1')
-
24
inflect.singular(/(cris|test)(is|es)$/i, '\1is')
-
24
inflect.singular(/^(a)x[ie]s$/i, '\1xis')
-
24
inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
-
24
inflect.singular(/(alias|status)(es)?$/i, '\1')
-
24
inflect.singular(/^(ox)en/i, '\1')
-
24
inflect.singular(/(vert|ind)ices$/i, '\1ex')
-
24
inflect.singular(/(matr)ices$/i, '\1ix')
-
24
inflect.singular(/(quiz)zes$/i, '\1')
-
24
inflect.singular(/(database)s$/i, '\1')
-
-
24
inflect.irregular("person", "people")
-
24
inflect.irregular("man", "men")
-
24
inflect.irregular("child", "children")
-
24
inflect.irregular("sex", "sexes")
-
24
inflect.irregular("move", "moves")
-
24
inflect.irregular("zombie", "zombies")
-
-
24
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
-
end
-
end
-
# frozen_string_literal: true
-
-
# in case active_support/inflector is required without the rest of active_support
-
23
require "active_support/inflector/inflections"
-
23
require "active_support/inflector/transliterate"
-
23
require "active_support/inflector/methods"
-
-
23
require "active_support/inflections"
-
23
require "active_support/core_ext/string/inflections"
-
# frozen_string_literal: true
-
-
24
require "concurrent/map"
-
24
require "active_support/i18n"
-
-
24
module ActiveSupport
-
24
module Inflector
-
24
extend self
-
-
# A singleton instance of this class is yielded by Inflector.inflections,
-
# which can then be used to specify additional inflection rules. If passed
-
# an optional locale, rules for other languages can be specified. The
-
# default locale is <tt>:en</tt>. Only rules for English are provided.
-
#
-
# ActiveSupport::Inflector.inflections(:en) do |inflect|
-
# inflect.plural /^(ox)$/i, '\1\2en'
-
# inflect.singular /^(ox)en/i, '\1'
-
#
-
# inflect.irregular 'octopus', 'octopi'
-
#
-
# inflect.uncountable 'equipment'
-
# end
-
#
-
# New rules are added at the top. So in the example above, the irregular
-
# rule for octopus will now be the first of the pluralization and
-
# singularization rules that is runs. This guarantees that your rules run
-
# before any of the rules that may already have been loaded.
-
24
class Inflections
-
24
@__instance__ = Concurrent::Map.new
-
-
24
class Uncountables < Array
-
24
def initialize
-
24
@regex_array = []
-
24
super
-
end
-
-
24
def delete(entry)
-
2016
super entry
-
2016
@regex_array.delete(to_regex(entry))
-
end
-
-
24
def <<(*word)
-
add(word)
-
end
-
-
24
def add(words)
-
25
words = words.flatten.map(&:downcase)
-
25
concat(words)
-
265
@regex_array += words.map { |word| to_regex(word) }
-
25
self
-
end
-
-
24
def uncountable?(str)
-
@regex_array.any? { |regex| regex.match? str }
-
end
-
-
24
private
-
24
def to_regex(string)
-
2256
/\b#{::Regexp.escape(string)}\Z/i
-
end
-
end
-
-
24
def self.instance(locale = :en)
-
1753
@__instance__[locale] ||= new
-
end
-
-
24
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
-
-
24
attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
-
-
24
def initialize
-
24
@plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
-
24
define_acronym_regex_patterns
-
end
-
-
# Private, for the test suite.
-
24
def initialize_dup(orig) # :nodoc:
-
%w(plurals singulars uncountables humans acronyms).each do |scope|
-
instance_variable_set("@#{scope}", orig.send(scope).dup)
-
end
-
define_acronym_regex_patterns
-
end
-
-
# Specifies a new acronym. An acronym must be specified as it will appear
-
# in a camelized string. An underscore string that contains the acronym
-
# will retain the acronym when passed to +camelize+, +humanize+, or
-
# +titleize+. A camelized string that contains the acronym will maintain
-
# the acronym when titleized or humanized, and will convert the acronym
-
# into a non-delimited single lowercase word when passed to +underscore+.
-
#
-
# acronym 'HTML'
-
# titleize 'html' # => 'HTML'
-
# camelize 'html' # => 'HTML'
-
# underscore 'MyHTML' # => 'my_html'
-
#
-
# The acronym, however, must occur as a delimited unit and not be part of
-
# another word for conversions to recognize it:
-
#
-
# acronym 'HTTP'
-
# camelize 'my_http_delimited' # => 'MyHTTPDelimited'
-
# camelize 'https' # => 'Https', not 'HTTPs'
-
# underscore 'HTTPS' # => 'http_s', not 'https'
-
#
-
# acronym 'HTTPS'
-
# camelize 'https' # => 'HTTPS'
-
# underscore 'HTTPS' # => 'https'
-
#
-
# Note: Acronyms that are passed to +pluralize+ will no longer be
-
# recognized, since the acronym will not occur as a delimited unit in the
-
# pluralized result. To work around this, you must specify the pluralized
-
# form as an acronym as well:
-
#
-
# acronym 'API'
-
# camelize(pluralize('api')) # => 'Apis'
-
#
-
# acronym 'APIs'
-
# camelize(pluralize('api')) # => 'APIs'
-
#
-
# +acronym+ may be used to specify any word that contains an acronym or
-
# otherwise needs to maintain a non-standard capitalization. The only
-
# restriction is that the word must begin with a capital letter.
-
#
-
# acronym 'RESTful'
-
# underscore 'RESTful' # => 'restful'
-
# underscore 'RESTfulController' # => 'restful_controller'
-
# titleize 'RESTfulController' # => 'RESTful Controller'
-
# camelize 'restful' # => 'RESTful'
-
# camelize 'restful_controller' # => 'RESTfulController'
-
#
-
# acronym 'McDonald'
-
# underscore 'McDonald' # => 'mcdonald'
-
# camelize 'mcdonald' # => 'McDonald'
-
24
def acronym(word)
-
@acronyms[word.downcase] = word
-
define_acronym_regex_patterns
-
end
-
-
# Specifies a new pluralization rule and its replacement. The rule can
-
# either be a string or a regular expression. The replacement should
-
# always be a string that may include references to the matched data from
-
# the rule.
-
24
def plural(rule, replacement)
-
792
@uncountables.delete(rule) if rule.is_a?(String)
-
792
@uncountables.delete(replacement)
-
792
@plurals.prepend([rule, replacement])
-
end
-
-
# Specifies a new singularization rule and its replacement. The rule can
-
# either be a string or a regular expression. The replacement should
-
# always be a string that may include references to the matched data from
-
# the rule.
-
24
def singular(rule, replacement)
-
936
@uncountables.delete(rule) if rule.is_a?(String)
-
936
@uncountables.delete(replacement)
-
936
@singulars.prepend([rule, replacement])
-
end
-
-
# Specifies a new irregular that applies to both pluralization and
-
# singularization at the same time. This can only be used for strings, not
-
# regular expressions. You simply pass the irregular in singular and
-
# plural form.
-
#
-
# irregular 'octopus', 'octopi'
-
# irregular 'person', 'people'
-
24
def irregular(singular, plural)
-
144
@uncountables.delete(singular)
-
144
@uncountables.delete(plural)
-
-
144
s0 = singular[0]
-
144
srest = singular[1..-1]
-
-
144
p0 = plural[0]
-
144
prest = plural[1..-1]
-
-
144
if s0.upcase == p0.upcase
-
144
plural(/(#{s0})#{srest}$/i, '\1' + prest)
-
144
plural(/(#{p0})#{prest}$/i, '\1' + prest)
-
-
144
singular(/(#{s0})#{srest}$/i, '\1' + srest)
-
144
singular(/(#{p0})#{prest}$/i, '\1' + srest)
-
else
-
plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
-
plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
-
plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
-
plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
-
-
singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
-
singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
-
singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
-
singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
-
end
-
end
-
-
# Specifies words that are uncountable and should not be inflected.
-
#
-
# uncountable 'money'
-
# uncountable 'money', 'information'
-
# uncountable %w( money information rice )
-
24
def uncountable(*words)
-
25
@uncountables.add(words)
-
end
-
-
# Specifies a humanized form of a string by a regular expression rule or
-
# by a string mapping. When using a regular expression based replacement,
-
# the normal humanize formatting is called after the replacement. When a
-
# string is used, the human form should be specified as desired (example:
-
# 'The name', not 'the_name').
-
#
-
# human /_cnt$/i, '\1_count'
-
# human 'legacy_col_person_name', 'Name'
-
24
def human(rule, replacement)
-
@humans.prepend([rule, replacement])
-
end
-
-
# Clears the loaded inflections within a given scope (default is
-
# <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
-
# options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
-
# <tt>:humans</tt>.
-
#
-
# clear :all
-
# clear :plurals
-
24
def clear(scope = :all)
-
case scope
-
when :all
-
@plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
-
else
-
instance_variable_set "@#{scope}", []
-
end
-
end
-
-
24
private
-
24
def define_acronym_regex_patterns
-
24
@acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
-
24
@acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
-
24
@acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
-
end
-
end
-
-
# Yields a singleton instance of Inflector::Inflections so you can specify
-
# additional inflector rules. If passed an optional locale, rules for other
-
# languages can be specified. If not specified, defaults to <tt>:en</tt>.
-
# Only rules for English are provided.
-
#
-
# ActiveSupport::Inflector.inflections(:en) do |inflect|
-
# inflect.uncountable 'rails'
-
# end
-
24
def inflections(locale = :en)
-
1753
if block_given?
-
26
yield Inflections.instance(locale)
-
else
-
1727
Inflections.instance(locale)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/inflections"
-
24
require "active_support/core_ext/object/blank"
-
-
24
module ActiveSupport
-
# The Inflector transforms words from singular to plural, class names to table
-
# names, modularized class names to ones without, and class names to foreign
-
# keys. The default inflections for pluralization, singularization, and
-
# uncountable words are kept in inflections.rb.
-
#
-
# The Rails core team has stated patches for the inflections library will not
-
# be accepted in order to avoid breaking legacy applications which may be
-
# relying on errant inflections. If you discover an incorrect inflection and
-
# require it for your application or wish to define rules for languages other
-
# than English, please correct or add them yourself (explained below).
-
24
module Inflector
-
24
extend self
-
-
# Returns the plural form of the word in the string.
-
#
-
# If passed an optional +locale+ parameter, the word will be
-
# pluralized using rules defined for that language. By default,
-
# this parameter is set to <tt>:en</tt>.
-
#
-
# pluralize('post') # => "posts"
-
# pluralize('octopus') # => "octopi"
-
# pluralize('sheep') # => "sheep"
-
# pluralize('words') # => "words"
-
# pluralize('CamelOctopus') # => "CamelOctopi"
-
# pluralize('ley', :es) # => "leyes"
-
24
def pluralize(word, locale = :en)
-
apply_inflections(word, inflections(locale).plurals, locale)
-
end
-
-
# The reverse of #pluralize, returns the singular form of a word in a
-
# string.
-
#
-
# If passed an optional +locale+ parameter, the word will be
-
# singularized using rules defined for that language. By default,
-
# this parameter is set to <tt>:en</tt>.
-
#
-
# singularize('posts') # => "post"
-
# singularize('octopi') # => "octopus"
-
# singularize('sheep') # => "sheep"
-
# singularize('word') # => "word"
-
# singularize('CamelOctopi') # => "CamelOctopus"
-
# singularize('leyes', :es) # => "ley"
-
24
def singularize(word, locale = :en)
-
apply_inflections(word, inflections(locale).singulars, locale)
-
end
-
-
# Converts strings to UpperCamelCase.
-
# If the +uppercase_first_letter+ parameter is set to false, then produces
-
# lowerCamelCase.
-
#
-
# Also converts '/' to '::' which is useful for converting
-
# paths to namespaces.
-
#
-
# camelize('active_model') # => "ActiveModel"
-
# camelize('active_model', false) # => "activeModel"
-
# camelize('active_model/errors') # => "ActiveModel::Errors"
-
# camelize('active_model/errors', false) # => "activeModel::Errors"
-
#
-
# As a rule of thumb you can think of +camelize+ as the inverse of
-
# #underscore, though there are cases where that does not hold:
-
#
-
# camelize(underscore('SSLError')) # => "SslError"
-
24
def camelize(term, uppercase_first_letter = true)
-
3
string = term.to_s
-
3
if uppercase_first_letter
-
6
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
-
else
-
string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
-
end
-
3
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
-
3
string.gsub!("/", "::")
-
3
string
-
end
-
-
# Makes an underscored, lowercase form from the expression in the string.
-
#
-
# Changes '::' to '/' to convert namespaces to paths.
-
#
-
# underscore('ActiveModel') # => "active_model"
-
# underscore('ActiveModel::Errors') # => "active_model/errors"
-
#
-
# As a rule of thumb you can think of +underscore+ as the inverse of
-
# #camelize, though there are cases where that does not hold:
-
#
-
# camelize(underscore('SSLError')) # => "SslError"
-
24
def underscore(camel_cased_word)
-
1723
return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
-
1723
word = camel_cased_word.to_s.gsub("::", "/")
-
1723
word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
-
1723
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
-
1723
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
1723
word.tr!("-", "_")
-
1723
word.downcase!
-
1723
word
-
end
-
-
# Tweaks an attribute name for display to end users.
-
#
-
# Specifically, performs these transformations:
-
#
-
# * Applies human inflection rules to the argument.
-
# * Deletes leading underscores, if any.
-
# * Removes a "_id" suffix if present.
-
# * Replaces underscores with spaces, if any.
-
# * Downcases all words except acronyms.
-
# * Capitalizes the first word.
-
# The capitalization of the first word can be turned off by setting the
-
# +:capitalize+ option to false (default is true).
-
#
-
# The trailing '_id' can be kept and capitalized by setting the
-
# optional parameter +keep_id_suffix+ to true (default is false).
-
#
-
# humanize('employee_salary') # => "Employee salary"
-
# humanize('author_id') # => "Author"
-
# humanize('author_id', capitalize: false) # => "author"
-
# humanize('_id') # => "Id"
-
# humanize('author_id', keep_id_suffix: true) # => "Author Id"
-
#
-
# If "SSL" was defined to be an acronym:
-
#
-
# humanize('ssl_error') # => "SSL error"
-
#
-
24
def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
-
result = lower_case_and_underscored_word.to_s.dup
-
-
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
-
-
result.sub!(/\A_+/, "")
-
unless keep_id_suffix
-
result.delete_suffix!("_id")
-
end
-
result.tr!("_", " ")
-
-
result.gsub!(/([a-z\d]*)/i) do |match|
-
"#{inflections.acronyms[match.downcase] || match.downcase}"
-
end
-
-
if capitalize
-
result.sub!(/\A\w/) { |match| match.upcase }
-
end
-
-
result
-
end
-
-
# Converts just the first character to uppercase.
-
#
-
# upcase_first('what a Lovely Day') # => "What a Lovely Day"
-
# upcase_first('w') # => "W"
-
# upcase_first('') # => ""
-
24
def upcase_first(string)
-
string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
-
end
-
-
# Capitalizes all the words and replaces some characters in the string to
-
# create a nicer looking title. +titleize+ is meant for creating pretty
-
# output. It is not used in the Rails internals.
-
#
-
# The trailing '_id','Id'.. can be kept and capitalized by setting the
-
# optional parameter +keep_id_suffix+ to true.
-
# By default, this parameter is false.
-
#
-
# +titleize+ is also aliased as +titlecase+.
-
#
-
# titleize('man from the boondocks') # => "Man From The Boondocks"
-
# titleize('x-men: the last stand') # => "X Men: The Last Stand"
-
# titleize('TheManWithoutAPast') # => "The Man Without A Past"
-
# titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
-
# titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
-
24
def titleize(word, keep_id_suffix: false)
-
humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
-
match.capitalize
-
end
-
end
-
-
# Creates the name of a table like Rails does for models to table names.
-
# This method uses the #pluralize method on the last word in the string.
-
#
-
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
-
# tableize('ham_and_egg') # => "ham_and_eggs"
-
# tableize('fancyCategory') # => "fancy_categories"
-
24
def tableize(class_name)
-
pluralize(underscore(class_name))
-
end
-
-
# Creates a class name from a plural table name like Rails does for table
-
# names to models. Note that this returns a string and not a Class (To
-
# convert to an actual class follow +classify+ with #constantize).
-
#
-
# classify('ham_and_eggs') # => "HamAndEgg"
-
# classify('posts') # => "Post"
-
#
-
# Singular names are not handled correctly:
-
#
-
# classify('calculus') # => "Calculu"
-
24
def classify(table_name)
-
# strip out any leading schema name
-
camelize(singularize(table_name.to_s.sub(/.*\./, "")))
-
end
-
-
# Replaces underscores with dashes in the string.
-
#
-
# dasherize('puni_puni') # => "puni-puni"
-
24
def dasherize(underscored_word)
-
underscored_word.tr("_", "-")
-
end
-
-
# Removes the module part from the expression in the string.
-
#
-
# demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
-
# demodulize('Inflections') # => "Inflections"
-
# demodulize('::Inflections') # => "Inflections"
-
# demodulize('') # => ""
-
#
-
# See also #deconstantize.
-
24
def demodulize(path)
-
path = path.to_s
-
if i = path.rindex("::")
-
path[(i + 2)..-1]
-
else
-
path
-
end
-
end
-
-
# Removes the rightmost segment from the constant expression in the string.
-
#
-
# deconstantize('Net::HTTP') # => "Net"
-
# deconstantize('::Net::HTTP') # => "::Net"
-
# deconstantize('String') # => ""
-
# deconstantize('::String') # => ""
-
# deconstantize('') # => ""
-
#
-
# See also #demodulize.
-
24
def deconstantize(path)
-
path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
-
end
-
-
# Creates a foreign key name from a class name.
-
# +separate_class_name_and_id_with_underscore+ sets whether
-
# the method should put '_' between the name and 'id'.
-
#
-
# foreign_key('Message') # => "message_id"
-
# foreign_key('Message', false) # => "messageid"
-
# foreign_key('Admin::Post') # => "post_id"
-
24
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
-
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
-
end
-
-
# Tries to find a constant with the name specified in the argument string.
-
#
-
# constantize('Module') # => Module
-
# constantize('Foo::Bar') # => Foo::Bar
-
#
-
# The name is assumed to be the one of a top-level constant, no matter
-
# whether it starts with "::" or not. No lexical context is taken into
-
# account:
-
#
-
# C = 'outside'
-
# module M
-
# C = 'inside'
-
# C # => 'inside'
-
# constantize('C') # => 'outside', same as ::C
-
# end
-
#
-
# NameError is raised when the name is not in CamelCase or the constant is
-
# unknown.
-
24
def constantize(camel_cased_word)
-
if camel_cased_word.blank? || !camel_cased_word.include?("::")
-
Object.const_get(camel_cased_word)
-
else
-
names = camel_cased_word.split("::")
-
-
# Trigger a built-in NameError exception including the ill-formed constant in the message.
-
Object.const_get(camel_cased_word) if names.empty?
-
-
# Remove the first blank element in case of '::ClassName' notation.
-
names.shift if names.size > 1 && names.first.empty?
-
-
names.inject(Object) do |constant, name|
-
if constant == Object
-
constant.const_get(name)
-
else
-
candidate = constant.const_get(name)
-
next candidate if constant.const_defined?(name, false)
-
next candidate unless Object.const_defined?(name)
-
-
# Go down the ancestors to check if it is owned directly. The check
-
# stops when we reach Object or the end of ancestors tree.
-
constant = constant.ancestors.inject(constant) do |const, ancestor|
-
break const if ancestor == Object
-
break ancestor if ancestor.const_defined?(name, false)
-
const
-
end
-
-
# owner is in Object, so raise
-
constant.const_get(name, false)
-
end
-
end
-
end
-
end
-
-
# Tries to find a constant with the name specified in the argument string.
-
#
-
# safe_constantize('Module') # => Module
-
# safe_constantize('Foo::Bar') # => Foo::Bar
-
#
-
# The name is assumed to be the one of a top-level constant, no matter
-
# whether it starts with "::" or not. No lexical context is taken into
-
# account:
-
#
-
# C = 'outside'
-
# module M
-
# C = 'inside'
-
# C # => 'inside'
-
# safe_constantize('C') # => 'outside', same as ::C
-
# end
-
#
-
# +nil+ is returned when the name is not in CamelCase or the constant (or
-
# part of it) is unknown.
-
#
-
# safe_constantize('blargle') # => nil
-
# safe_constantize('UnknownModule') # => nil
-
# safe_constantize('UnknownModule::Foo::Bar') # => nil
-
24
def safe_constantize(camel_cased_word)
-
constantize(camel_cased_word)
-
rescue NameError => e
-
raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
-
e.name.to_s == camel_cased_word.to_s)
-
rescue LoadError => e
-
message = e.respond_to?(:original_message) ? e.original_message : e.message
-
raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
-
end
-
-
# Returns the suffix that should be added to a number to denote the position
-
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# ordinal(1) # => "st"
-
# ordinal(2) # => "nd"
-
# ordinal(1002) # => "nd"
-
# ordinal(1003) # => "rd"
-
# ordinal(-11) # => "th"
-
# ordinal(-1021) # => "st"
-
24
def ordinal(number)
-
I18n.translate("number.nth.ordinals", number: number)
-
end
-
-
# Turns a number into an ordinal string used to denote the position in an
-
# ordered sequence such as 1st, 2nd, 3rd, 4th.
-
#
-
# ordinalize(1) # => "1st"
-
# ordinalize(2) # => "2nd"
-
# ordinalize(1002) # => "1002nd"
-
# ordinalize(1003) # => "1003rd"
-
# ordinalize(-11) # => "-11th"
-
# ordinalize(-1021) # => "-1021st"
-
24
def ordinalize(number)
-
I18n.translate("number.nth.ordinalized", number: number)
-
end
-
-
24
private
-
# Mounts a regular expression, returned as a string to ease interpolation,
-
# that will match part by part the given constant.
-
#
-
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
-
# const_regexp("::") # => "::"
-
24
def const_regexp(camel_cased_word)
-
parts = camel_cased_word.split("::")
-
-
return Regexp.escape(camel_cased_word) if parts.blank?
-
-
last = parts.pop
-
-
parts.reverse!.inject(last) do |acc, part|
-
part.empty? ? acc : "#{part}(::#{acc})?"
-
end
-
end
-
-
# Applies inflection rules for +singularize+ and +pluralize+.
-
#
-
# If passed an optional +locale+ parameter, the uncountables will be
-
# found for that locale.
-
#
-
# apply_inflections('post', inflections.plurals, :en) # => "posts"
-
# apply_inflections('posts', inflections.singulars, :en) # => "post"
-
24
def apply_inflections(word, rules, locale = :en)
-
result = word.to_s.dup
-
-
if word.empty? || inflections(locale).uncountables.uncountable?(result)
-
result
-
else
-
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
-
result
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/string/multibyte"
-
23
require "active_support/i18n"
-
-
23
module ActiveSupport
-
23
module Inflector
-
23
ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
-
-
# Replaces non-ASCII characters with an ASCII approximation, or if none
-
# exists, a replacement character which defaults to "?".
-
#
-
# transliterate('Ærøskøbing')
-
# # => "AEroskobing"
-
#
-
# Default approximations are provided for Western/Latin characters,
-
# e.g, "ø", "ñ", "é", "ß", etc.
-
#
-
# This method is I18n aware, so you can set up custom approximations for a
-
# locale. This can be useful, for example, to transliterate German's "ü"
-
# and "ö" to "ue" and "oe", or to add support for transliterating Russian
-
# to ASCII.
-
#
-
# In order to make your custom transliterations available, you must set
-
# them as the <tt>i18n.transliterate.rule</tt> i18n key:
-
#
-
# # Store the transliterations in locales/de.yml
-
# i18n:
-
# transliterate:
-
# rule:
-
# ü: "ue"
-
# ö: "oe"
-
#
-
# # Or set them using Ruby
-
# I18n.backend.store_translations(:de, i18n: {
-
# transliterate: {
-
# rule: {
-
# 'ü' => 'ue',
-
# 'ö' => 'oe'
-
# }
-
# }
-
# })
-
#
-
# The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that
-
# maps characters to ASCII approximations as shown above, or, for more
-
# complex requirements, a Proc:
-
#
-
# I18n.backend.store_translations(:de, i18n: {
-
# transliterate: {
-
# rule: ->(string) { MyTransliterator.transliterate(string) }
-
# }
-
# })
-
#
-
# Now you can have different transliterations for each locale:
-
#
-
# transliterate('Jürgen', locale: :en)
-
# # => "Jurgen"
-
#
-
# transliterate('Jürgen', locale: :de)
-
# # => "Juergen"
-
#
-
# Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
-
# Other encodings will raise an ArgumentError.
-
23
def transliterate(string, replacement = "?", locale: nil)
-
string = string.dup if string.frozen?
-
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
-
raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
-
-
input_encoding = string.encoding
-
-
# US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
-
# US-ASCII is given. This way we can let tidy_bytes handle the string
-
# in the same way as we do for UTF-8
-
string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII
-
-
# GB18030 is Unicode compatible but is not a direct mapping so needs to be
-
# transcoded. Using invalid/undef :replace will result in loss of data in
-
# the event of invalid characters, but since tidy_bytes will replace
-
# invalid/undef with a "?" we're safe to do the same beforehand
-
string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030
-
-
transliterated = I18n.transliterate(
-
ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
-
replacement: replacement,
-
locale: locale
-
)
-
-
# Restore the string encoding of the input if it was not UTF-8.
-
# Apply invalid/undef :replace as tidy_bytes does
-
transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding
-
-
transliterated
-
end
-
-
# Replaces special characters in a string so that it may be used as part of
-
# a 'pretty' URL.
-
#
-
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
-
# parameterize("^très|Jolie-- ") # => "tres-jolie"
-
#
-
# To use a custom separator, override the +separator+ argument.
-
#
-
# parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
-
# parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
-
#
-
# To preserve the case of the characters in a string, use the +preserve_case+ argument.
-
#
-
# parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
-
# parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
-
#
-
# It preserves dashes and underscores unless they are used as separators:
-
#
-
# parameterize("^très|Jolie__ ") # => "tres-jolie__"
-
# parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
-
# parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
-
#
-
# If the optional parameter +locale+ is specified,
-
# the word will be parameterized as a word of that language.
-
# By default, this parameter is set to <tt>nil</tt> and it will use
-
# the configured <tt>I18n.locale</tt>.
-
23
def parameterize(string, separator: "-", preserve_case: false, locale: nil)
-
# Replace accented chars with their ASCII equivalents.
-
parameterized_string = transliterate(string, locale: locale)
-
-
# Turn unwanted chars into the separator.
-
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
-
-
unless separator.nil? || separator.empty?
-
if separator == "-"
-
re_duplicate_separator = /-{2,}/
-
re_leading_trailing_separator = /^-|-$/i
-
else
-
re_sep = Regexp.escape(separator)
-
re_duplicate_separator = /#{re_sep}{2,}/
-
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
-
end
-
# No more than one of the separator in a row.
-
parameterized_string.gsub!(re_duplicate_separator, separator)
-
# Remove leading/trailing separator.
-
parameterized_string.gsub!(re_leading_trailing_separator, "")
-
end
-
-
parameterized_string.downcase! unless preserve_case
-
parameterized_string
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/json/decoding"
-
2
require "active_support/json/encoding"
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/module/attribute_accessors"
-
2
require "active_support/core_ext/module/delegation"
-
2
require "json"
-
-
2
module ActiveSupport
-
# Look for and parse json strings that look like ISO 8601 times.
-
2
mattr_accessor :parse_json_times
-
-
2
module JSON
-
# matches YAML-formatted dates
-
2
DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/
-
2
DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
-
-
2
class << self
-
# Parses a JSON string (JavaScript Object Notation) into a hash.
-
# See http://www.json.org for more info.
-
#
-
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
-
# => {"team" => "rails", "players" => "36"}
-
2
def decode(json)
-
data = ::JSON.parse(json, quirks_mode: true)
-
-
if ActiveSupport.parse_json_times
-
convert_dates_from(data)
-
else
-
data
-
end
-
end
-
-
# Returns the class of the error that will be raised when there is an
-
# error in decoding JSON. Using this method means you won't directly
-
# depend on the ActiveSupport's JSON implementation, in case it changes
-
# in the future.
-
#
-
# begin
-
# obj = ActiveSupport::JSON.decode(some_string)
-
# rescue ActiveSupport::JSON.parse_error
-
# Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
-
# end
-
2
def parse_error
-
::JSON::ParserError
-
end
-
-
2
private
-
2
def convert_dates_from(data)
-
case data
-
when nil
-
nil
-
when DATE_REGEX
-
begin
-
Date.parse(data)
-
rescue ArgumentError
-
data
-
end
-
when DATETIME_REGEX
-
begin
-
Time.zone.parse(data)
-
rescue ArgumentError
-
data
-
end
-
when Array
-
data.map! { |d| convert_dates_from(d) }
-
when Hash
-
data.each do |key, value|
-
data[key] = convert_dates_from(value)
-
end
-
else
-
data
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/object/json"
-
2
require "active_support/core_ext/module/delegation"
-
-
2
module ActiveSupport
-
2
class << self
-
2
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
-
:time_precision, :time_precision=,
-
:escape_html_entities_in_json, :escape_html_entities_in_json=,
-
:json_encoder, :json_encoder=,
-
to: :'ActiveSupport::JSON::Encoding'
-
end
-
-
2
module JSON
-
# Dumps objects in JSON (JavaScript Object Notation).
-
# See http://www.json.org for more info.
-
#
-
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
-
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
-
2
def self.encode(value, options = nil)
-
Encoding.json_encoder.new(options).encode(value)
-
end
-
-
2
module Encoding #:nodoc:
-
2
class JSONGemEncoder #:nodoc:
-
2
attr_reader :options
-
-
2
def initialize(options = nil)
-
@options = options || {}
-
end
-
-
# Encode the given object into a JSON string
-
2
def encode(value)
-
stringify jsonify value.as_json(options.dup)
-
end
-
-
2
private
-
# Rails does more escaping than the JSON gem natively does (we
-
# escape \u2028 and \u2029 and optionally >, <, & to work around
-
# certain browser problems).
-
2
ESCAPED_CHARS = {
-
"\u2028" => '\u2028',
-
"\u2029" => '\u2029',
-
">" => '\u003e',
-
"<" => '\u003c',
-
"&" => '\u0026',
-
}
-
-
2
ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
-
2
ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
-
-
# This class wraps all the strings we see and does the extra escaping
-
2
class EscapedString < String #:nodoc:
-
2
def to_json(*)
-
if Encoding.escape_html_entities_in_json
-
s = super
-
s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
-
s
-
else
-
s = super
-
s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
-
s
-
end
-
end
-
-
2
def to_s
-
self
-
end
-
end
-
-
# Mark these as private so we don't leak encoding-specific constructs
-
2
private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
-
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
-
-
# Convert an object into a "JSON-ready" representation composed of
-
# primitives like Hash, Array, String, Numeric,
-
# and +true+/+false+/+nil+.
-
# Recursively calls #as_json to the object to recursively build a
-
# fully JSON-ready object.
-
#
-
# This allows developers to implement #as_json without having to
-
# worry about what base types of objects they are allowed to return
-
# or having to remember to call #as_json recursively.
-
#
-
# Note: the +options+ hash passed to +object.to_json+ is only passed
-
# to +object.as_json+, not any of this method's recursive +#as_json+
-
# calls.
-
2
def jsonify(value)
-
case value
-
when String
-
EscapedString.new(value)
-
when Numeric, NilClass, TrueClass, FalseClass
-
value.as_json
-
when Hash
-
result = {}
-
value.each do |k, v|
-
result[jsonify(k)] = jsonify(v)
-
end
-
result
-
when Array
-
value.map { |v| jsonify(v) }
-
else
-
jsonify value.as_json
-
end
-
end
-
-
# Encode a "jsonified" Ruby data structure using the JSON gem
-
2
def stringify(jsonified)
-
::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
-
end
-
end
-
-
2
class << self
-
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
-
# to the Active Support legacy format.
-
2
attr_accessor :use_standard_json_time_format
-
-
# If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e)
-
# as a safety measure.
-
2
attr_accessor :escape_html_entities_in_json
-
-
# Sets the precision of encoded time values.
-
# Defaults to 3 (equivalent to millisecond precision)
-
2
attr_accessor :time_precision
-
-
# Sets the encoder used by Rails to encode Ruby objects into JSON strings
-
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
-
2
attr_accessor :json_encoder
-
end
-
-
2
self.use_standard_json_time_format = true
-
2
self.escape_html_entities_in_json = true
-
2
self.json_encoder = JSONGemEncoder
-
2
self.time_precision = 3
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "concurrent/map"
-
require "openssl"
-
-
module ActiveSupport
-
# KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2.
-
# It can be used to derive a number of keys for various purposes from a given secret.
-
# This lets Rails applications have a single secure secret, but avoid reusing that
-
# key in multiple incompatible contexts.
-
class KeyGenerator
-
def initialize(secret, options = {})
-
@secret = secret
-
# The default iterations are higher than required for our key derivation uses
-
# on the off chance someone uses this for password storage
-
@iterations = options[:iterations] || 2**16
-
end
-
-
# Returns a derived key suitable for use. The default key_size is chosen
-
# to be compatible with the default settings of ActiveSupport::MessageVerifier.
-
# i.e. OpenSSL::Digest::SHA1#block_length
-
def generate_key(salt, key_size = 64)
-
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
-
end
-
end
-
-
# CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
-
# re-executing the key generation process when it's called using the same salt and
-
# key_size.
-
class CachingKeyGenerator
-
def initialize(key_generator)
-
@key_generator = key_generator
-
@cache_keys = Concurrent::Map.new
-
end
-
-
# Returns a derived key suitable for use.
-
def generate_key(*args)
-
@cache_keys[args.join("|")] ||= @key_generator.generate_key(*args)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
module ActiveSupport
-
# lazy_load_hooks allows Rails to lazily load a lot of components and thus
-
# making the app boot faster. Because of this feature now there is no need to
-
# require <tt>ActiveRecord::Base</tt> at boot time purely to apply
-
# configuration. Instead a hook is registered that applies configuration once
-
# <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
-
# used as example but this feature can be applied elsewhere too.
-
#
-
# Here is an example where +on_load+ method is called to register a hook.
-
#
-
# initializer 'active_record.initialize_timezone' do
-
# ActiveSupport.on_load(:active_record) do
-
# self.time_zone_aware_attributes = true
-
# self.default_timezone = :utc
-
# end
-
# end
-
#
-
# When the entirety of +ActiveRecord::Base+ has been
-
# evaluated then +run_load_hooks+ is invoked. The very last line of
-
# +ActiveRecord::Base+ is:
-
#
-
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
-
24
module LazyLoadHooks
-
24
def self.extended(base) # :nodoc:
-
24
base.class_eval do
-
71
@load_hooks = Hash.new { |h, k| h[k] = [] }
-
71
@loaded = Hash.new { |h, k| h[k] = [] }
-
24
@run_once = Hash.new { |h, k| h[k] = [] }
-
end
-
end
-
-
# Declares a block that will be executed when a Rails component is fully
-
# loaded.
-
#
-
# Options:
-
#
-
# * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
-
# * <tt>:run_once</tt> - Given +block+ will run only once.
-
24
def on_load(name, options = {}, &block)
-
@loaded[name].each do |base|
-
execute_hook(name, base, options, block)
-
end
-
-
@load_hooks[name] << [block, options]
-
end
-
-
24
def run_load_hooks(name, base = Object)
-
47
@loaded[name] << base
-
47
@load_hooks[name].each do |hook, options|
-
execute_hook(name, base, options, hook)
-
end
-
end
-
-
24
private
-
24
def with_execution_control(name, block, once)
-
unless @run_once[name].include?(block)
-
@run_once[name] << block if once
-
-
yield
-
end
-
end
-
-
24
def execute_hook(name, base, options, block)
-
with_execution_control(name, block, options[:run_once]) do
-
if options[:yield]
-
block.call(base)
-
else
-
if base.is_a?(Module)
-
base.class_eval(&block)
-
else
-
base.instance_eval(&block)
-
end
-
end
-
end
-
end
-
end
-
-
24
extend LazyLoadHooks
-
end
-
# frozen_string_literal: true
-
-
{
-
en: {
-
number: {
-
nth: {
-
ordinals: lambda do |_key, options|
-
number = options[:number]
-
case number
-
when 1; "st"
-
when 2; "nd"
-
when 3; "rd"
-
when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; "th"
-
else
-
num_modulo = number.to_i.abs % 100
-
num_modulo %= 10 if num_modulo > 13
-
case num_modulo
-
when 1; "st"
-
when 2; "nd"
-
when 3; "rd"
-
else "th"
-
end
-
end
-
end,
-
-
ordinalized: lambda do |_key, options|
-
number = options[:number]
-
"#{number}#{ActiveSupport::Inflector.ordinal(number)}"
-
end
-
}
-
}
-
}
-
}
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/module/attribute_accessors"
-
1
require "active_support/core_ext/class/attribute"
-
1
require "active_support/subscriber"
-
-
1
module ActiveSupport
-
# <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
-
# <tt>ActiveSupport::Notifications</tt> with the sole purpose of logging them.
-
# The log subscriber dispatches notifications to a registered object based
-
# on its given namespace.
-
#
-
# An example would be Active Record log subscriber responsible for logging
-
# queries:
-
#
-
# module ActiveRecord
-
# class LogSubscriber < ActiveSupport::LogSubscriber
-
# def sql(event)
-
# info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
-
# end
-
# end
-
# end
-
#
-
# And it's finally registered as:
-
#
-
# ActiveRecord::LogSubscriber.attach_to :active_record
-
#
-
# Since we need to know all instance methods before attaching the log
-
# subscriber, the line above should be called after your
-
# <tt>ActiveRecord::LogSubscriber</tt> definition.
-
#
-
# A logger also needs to be set with <tt>ActiveRecord::LogSubscriber.logger=</tt>.
-
# This is assigned automatically in a Rails environment.
-
#
-
# After configured, whenever a <tt>"sql.active_record"</tt> notification is published,
-
# it will properly dispatch the event
-
# (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
-
#
-
# Being an <tt>ActiveSupport::Notifications</tt> consumer,
-
# <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
-
# instrumented code raises an exception. It is common to log a different
-
# message in case of an error, and this can be achieved by extending
-
# the previous example:
-
#
-
# module ActiveRecord
-
# class LogSubscriber < ActiveSupport::LogSubscriber
-
# def sql(event)
-
# exception = event.payload[:exception]
-
#
-
# if exception
-
# exception_object = event.payload[:exception_object]
-
#
-
# error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \
-
# "(#{exception_object.backtrace.first})"
-
# else
-
# # standard logger code
-
# end
-
# end
-
# end
-
# end
-
#
-
# Log subscriber also has some helpers to deal with logging and automatically
-
# flushes all logs when the request finishes
-
# (via <tt>action_dispatch.callback</tt> notification) in a Rails environment.
-
1
class LogSubscriber < Subscriber
-
# Embed in a String to clear all previous ANSI sequences.
-
1
CLEAR = "\e[0m"
-
1
BOLD = "\e[1m"
-
-
# Colors
-
1
BLACK = "\e[30m"
-
1
RED = "\e[31m"
-
1
GREEN = "\e[32m"
-
1
YELLOW = "\e[33m"
-
1
BLUE = "\e[34m"
-
1
MAGENTA = "\e[35m"
-
1
CYAN = "\e[36m"
-
1
WHITE = "\e[37m"
-
-
1
mattr_accessor :colorize_logging, default: true
-
-
1
class << self
-
1
def logger
-
@logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
-
Rails.logger
-
end
-
end
-
-
1
attr_writer :logger
-
-
1
def log_subscribers
-
subscribers
-
end
-
-
# Flush all log_subscribers' logger.
-
1
def flush_all!
-
logger.flush if logger.respond_to?(:flush)
-
end
-
end
-
-
1
def logger
-
LogSubscriber.logger
-
end
-
-
1
def start(name, id, payload)
-
super if logger
-
end
-
-
1
def finish(name, id, payload)
-
super if logger
-
rescue => e
-
if logger
-
logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
-
end
-
end
-
-
1
private
-
1
%w(info debug warn error fatal unknown).each do |level|
-
6
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{level}(progname = nil, &block)
-
logger.#{level}(progname, &block) if logger
-
end
-
METHOD
-
end
-
-
# Set color by using a symbol or one of the defined constants. If a third
-
# option is set to +true+, it also adds bold to the string. This is based
-
# on the Highline implementation and will automatically append CLEAR to the
-
# end of the returned String.
-
1
def color(text, color, bold = false) # :doc:
-
return text unless colorize_logging
-
color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
-
bold = bold ? BOLD : ""
-
"#{bold}#{color}#{text}#{CLEAR}"
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/log_subscriber"
-
1
require "active_support/logger"
-
1
require "active_support/notifications"
-
-
1
module ActiveSupport
-
1
class LogSubscriber
-
# Provides some helpers to deal with testing log subscribers by setting up
-
# notifications. Take for instance Active Record subscriber tests:
-
#
-
# class SyncLogSubscriberTest < ActiveSupport::TestCase
-
# include ActiveSupport::LogSubscriber::TestHelper
-
#
-
# setup do
-
# ActiveRecord::LogSubscriber.attach_to(:active_record)
-
# end
-
#
-
# def test_basic_query_logging
-
# Developer.all.to_a
-
# wait
-
# assert_equal 1, @logger.logged(:debug).size
-
# assert_match(/Developer Load/, @logger.logged(:debug).last)
-
# assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last)
-
# end
-
# end
-
#
-
# All you need to do is to ensure that your log subscriber is added to
-
# Rails::Subscriber, as in the second line of the code above. The test
-
# helpers are responsible for setting up the queue, subscriptions and
-
# turning colors in logs off.
-
#
-
# The messages are available in the @logger instance, which is a logger with
-
# limited powers (it actually does not send anything to your output), and
-
# you can collect them doing @logger.logged(level), where level is the level
-
# used in logging, like info, debug, warn and so on.
-
1
module TestHelper
-
1
def setup # :nodoc:
-
@logger = MockLogger.new
-
@notifier = ActiveSupport::Notifications::Fanout.new
-
-
ActiveSupport::LogSubscriber.colorize_logging = false
-
-
@old_notifier = ActiveSupport::Notifications.notifier
-
set_logger(@logger)
-
ActiveSupport::Notifications.notifier = @notifier
-
end
-
-
1
def teardown # :nodoc:
-
set_logger(nil)
-
ActiveSupport::Notifications.notifier = @old_notifier
-
end
-
-
1
class MockLogger
-
1
include ActiveSupport::Logger::Severity
-
-
1
attr_reader :flush_count
-
1
attr_accessor :level
-
-
1
def initialize(level = DEBUG)
-
@flush_count = 0
-
@level = level
-
@logged = Hash.new { |h, k| h[k] = [] }
-
end
-
-
1
def method_missing(level, message = nil)
-
if block_given?
-
@logged[level] << yield
-
else
-
@logged[level] << message
-
end
-
end
-
-
1
def logged(level)
-
@logged[level].compact.map { |l| l.to_s.strip }
-
end
-
-
1
def flush
-
@flush_count += 1
-
end
-
-
1
ActiveSupport::Logger::Severity.constants.each do |severity|
-
6
class_eval <<-EOT, __FILE__, __LINE__ + 1
-
def #{severity.downcase}?
-
#{severity} >= @level
-
end
-
EOT
-
end
-
end
-
-
# Wait notifications to be published.
-
1
def wait
-
@notifier.wait
-
end
-
-
# Overwrite if you use another logger in your log subscriber.
-
#
-
# def logger
-
# ActiveRecord::Base.logger = @logger
-
# end
-
1
def set_logger(logger)
-
ActiveSupport::LogSubscriber.logger = logger
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/logger_silence"
-
24
require "active_support/logger_thread_safe_level"
-
24
require "logger"
-
-
24
module ActiveSupport
-
24
class Logger < ::Logger
-
24
include LoggerSilence
-
-
# Returns true if the logger destination matches one of the sources
-
#
-
# logger = Logger.new(STDOUT)
-
# ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
-
# # => true
-
24
def self.logger_outputs_to?(logger, *sources)
-
logdev = logger.instance_variable_get(:@logdev)
-
logger_source = logdev.dev if logdev.respond_to?(:dev)
-
sources.any? { |source| source == logger_source }
-
end
-
-
# Broadcasts logs to multiple loggers.
-
24
def self.broadcast(logger) # :nodoc:
-
Module.new do
-
define_method(:add) do |*args, &block|
-
logger.add(*args, &block)
-
super(*args, &block)
-
end
-
-
define_method(:<<) do |x|
-
logger << x
-
super(x)
-
end
-
-
define_method(:close) do
-
logger.close
-
super()
-
end
-
-
define_method(:progname=) do |name|
-
logger.progname = name
-
super(name)
-
end
-
-
define_method(:formatter=) do |formatter|
-
logger.formatter = formatter
-
super(formatter)
-
end
-
-
define_method(:level=) do |level|
-
logger.level = level
-
super(level)
-
end
-
-
define_method(:local_level=) do |level|
-
logger.local_level = level if logger.respond_to?(:local_level=)
-
super(level) if respond_to?(:local_level=)
-
end
-
-
define_method(:silence) do |level = Logger::ERROR, &block|
-
if logger.respond_to?(:silence)
-
logger.silence(level) do
-
if defined?(super)
-
super(level, &block)
-
else
-
block.call(self)
-
end
-
end
-
else
-
if defined?(super)
-
super(level, &block)
-
else
-
block.call(self)
-
end
-
end
-
end
-
end
-
end
-
-
24
def initialize(*args, **kwargs)
-
super
-
@formatter = SimpleFormatter.new
-
end
-
-
# Simple formatter which only displays the message.
-
24
class SimpleFormatter < ::Logger::Formatter
-
# This method is invoked when a log event occurs
-
24
def call(severity, timestamp, progname, msg)
-
"#{String === msg ? msg : msg.inspect}\n"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/concern"
-
24
require "active_support/core_ext/module/attribute_accessors"
-
24
require "active_support/logger_thread_safe_level"
-
-
24
module LoggerSilence
-
24
extend ActiveSupport::Concern
-
-
24
included do
-
ActiveSupport::Deprecation.warn(
-
"Including LoggerSilence is deprecated and will be removed in Rails 6.1. " \
-
"Please use `ActiveSupport::LoggerSilence` instead"
-
)
-
-
include ActiveSupport::LoggerSilence
-
end
-
end
-
-
24
module ActiveSupport
-
24
module LoggerSilence
-
24
extend ActiveSupport::Concern
-
-
24
included do
-
26
cattr_accessor :silencer, default: true
-
26
include ActiveSupport::LoggerThreadSafeLevel
-
end
-
-
# Silences the logger for the duration of the block.
-
24
def silence(severity = Logger::ERROR)
-
silencer ? log_at(severity) { yield self } : yield(self)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/concern"
-
24
require "active_support/core_ext/module/attribute_accessors"
-
24
require "concurrent"
-
24
require "fiber"
-
-
24
module ActiveSupport
-
24
module LoggerThreadSafeLevel # :nodoc:
-
24
extend ActiveSupport::Concern
-
-
24
included do
-
26
cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false
-
end
-
-
24
Logger::Severity.constants.each do |severity|
-
144
class_eval(<<-EOT, __FILE__, __LINE__ + 1)
-
def #{severity.downcase}? # def debug?
-
Logger::#{severity} >= level # DEBUG >= level
-
end # end
-
EOT
-
end
-
-
24
def after_initialize
-
ActiveSupport::Deprecation.warn(
-
"Logger don't need to call #after_initialize directly anymore. It will be deprecated without replacement in " \
-
"Rails 6.1."
-
)
-
end
-
-
24
def local_log_id
-
Fiber.current.__id__
-
end
-
-
24
def local_level
-
self.class.local_levels[local_log_id]
-
end
-
-
24
def local_level=(level)
-
case level
-
when Integer
-
self.class.local_levels[local_log_id] = level
-
when Symbol
-
self.class.local_levels[local_log_id] = Logger::Severity.const_get(level.to_s.upcase)
-
when nil
-
self.class.local_levels.delete(local_log_id)
-
else
-
raise ArgumentError, "Invalid log level: #{level.inspect}"
-
end
-
end
-
-
24
def level
-
local_level || super
-
end
-
-
# Change the thread-local level for the duration of the given block.
-
24
def log_at(level)
-
old_local_level, self.local_level = local_level, level
-
yield
-
ensure
-
self.local_level = old_local_level
-
end
-
-
# Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
-
# FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
-
24
def add(severity, message = nil, progname = nil, &block) #:nodoc:
-
severity ||= UNKNOWN
-
progname ||= @progname
-
-
return true if @logdev.nil? || severity < level
-
-
if message.nil?
-
if block_given?
-
message = yield
-
else
-
message = progname
-
progname = @progname
-
end
-
end
-
-
@logdev.write \
-
format_message(format_severity(severity), Time.now, progname, message)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "openssl"
-
2
require "base64"
-
2
require "active_support/core_ext/module/attribute_accessors"
-
2
require "active_support/message_verifier"
-
2
require "active_support/messages/metadata"
-
-
2
module ActiveSupport
-
# MessageEncryptor is a simple way to encrypt values which get stored
-
# somewhere you don't trust.
-
#
-
# The cipher text and initialization vector are base64 encoded and returned
-
# to you.
-
#
-
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
-
# where you don't want users to be able to determine the value of the payload.
-
#
-
# len = ActiveSupport::MessageEncryptor.key_len
-
# salt = SecureRandom.random_bytes(len)
-
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
-
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
-
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
-
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
-
#
-
# === Confining messages to a specific purpose
-
#
-
# By default any message can be used throughout your app. But they can also be
-
# confined to a specific +:purpose+.
-
#
-
# token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
-
#
-
# Then that same purpose must be passed when verifying to get the data back out:
-
#
-
# crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair"
-
# crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
-
# crypt.decrypt_and_verify(token) # => nil
-
#
-
# Likewise, if a message has no purpose it won't be returned when verifying with
-
# a specific purpose.
-
#
-
# token = crypt.encrypt_and_sign("the conversation is lively")
-
# crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
-
# crypt.decrypt_and_verify(token) # => "the conversation is lively"
-
#
-
# === Making messages expire
-
#
-
# By default messages last forever and verifying one year from now will still
-
# return the original value. But messages can be set to expire at a given
-
# time with +:expires_in+ or +:expires_at+.
-
#
-
# crypt.encrypt_and_sign(parcel, expires_in: 1.month)
-
# crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
-
#
-
# Then the messages can be verified and returned up to the expire time.
-
# Thereafter, verifying returns +nil+.
-
#
-
# === Rotating keys
-
#
-
# MessageEncryptor also supports rotating out old configurations by falling
-
# back to a stack of encryptors. Call +rotate+ to build and add an encryptor
-
# so +decrypt_and_verify+ will also try the fallback.
-
#
-
# By default any rotated encryptors use the values of the primary
-
# encryptor unless specified otherwise.
-
#
-
# You'd give your encryptor the new defaults:
-
#
-
# crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
-
#
-
# Then gradually rotate the old values out by adding them as fallbacks. Any message
-
# generated with the old values will then work until the rotation is removed.
-
#
-
# crypt.rotate old_secret # Fallback to an old secret instead of @secret.
-
# crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
-
#
-
# Though if both the secret and the cipher was changed at the same time,
-
# the above should be combined into:
-
#
-
# crypt.rotate old_secret, cipher: "aes-256-cbc"
-
2
class MessageEncryptor
-
2
prepend Messages::Rotator::Encryptor
-
-
2
cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
-
-
2
class << self
-
2
def default_cipher #:nodoc:
-
if use_authenticated_message_encryption
-
"aes-256-gcm"
-
else
-
"aes-256-cbc"
-
end
-
end
-
end
-
-
2
module NullSerializer #:nodoc:
-
2
def self.load(value)
-
value
-
end
-
-
2
def self.dump(value)
-
value
-
end
-
end
-
-
2
module NullVerifier #:nodoc:
-
2
def self.verify(value)
-
value
-
end
-
-
2
def self.generate(value)
-
value
-
end
-
end
-
-
2
class InvalidMessage < StandardError; end
-
2
OpenSSLCipherError = OpenSSL::Cipher::CipherError
-
-
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
-
# the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
-
# bits. If you are using a user-entered secret, you can generate a suitable
-
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
-
# derivation function.
-
#
-
# First additional parameter is used as the signature key for +MessageVerifier+.
-
# This allows you to specify keys to encrypt and sign data.
-
#
-
# ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
-
#
-
# Options:
-
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
-
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
-
# * <tt>:digest</tt> - String of digest to use for signing. Default is
-
# +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
-
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
-
2
def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil)
-
@secret = secret
-
@sign_secret = sign_secret
-
@cipher = cipher || self.class.default_cipher
-
@digest = digest || "SHA1" unless aead_mode?
-
@verifier = resolve_verifier
-
@serializer = serializer || Marshal
-
end
-
-
# Encrypt and sign a message. We need to sign the message in order to avoid
-
# padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
-
2
def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil)
-
verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose))
-
end
-
-
# Decrypt and verify a message. We need to verify the message in order to
-
# avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
-
2
def decrypt_and_verify(data, purpose: nil, **)
-
_decrypt(verifier.verify(data), purpose)
-
end
-
-
# Given a cipher, returns the key length of the cipher to help generate the key of desired size
-
2
def self.key_len(cipher = default_cipher)
-
OpenSSL::Cipher.new(cipher).key_len
-
end
-
-
2
private
-
2
def _encrypt(value, **metadata_options)
-
cipher = new_cipher
-
cipher.encrypt
-
cipher.key = @secret
-
-
# Rely on OpenSSL for the initialization vector
-
iv = cipher.random_iv
-
cipher.auth_data = "" if aead_mode?
-
-
encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options))
-
encrypted_data << cipher.final
-
-
blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
-
blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
-
blob
-
end
-
-
2
def _decrypt(encrypted_message, purpose)
-
cipher = new_cipher
-
encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) }
-
-
# Currently the OpenSSL bindings do not raise an error if auth_tag is
-
# truncated, which would allow an attacker to easily forge it. See
-
# https://github.com/ruby/openssl/issues/63
-
raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)
-
-
cipher.decrypt
-
cipher.key = @secret
-
cipher.iv = iv
-
if aead_mode?
-
cipher.auth_tag = auth_tag
-
cipher.auth_data = ""
-
end
-
-
decrypted_data = cipher.update(encrypted_data)
-
decrypted_data << cipher.final
-
-
message = Messages::Metadata.verify(decrypted_data, purpose)
-
@serializer.load(message) if message
-
rescue OpenSSLCipherError, TypeError, ArgumentError
-
raise InvalidMessage
-
end
-
-
2
def new_cipher
-
OpenSSL::Cipher.new(@cipher)
-
end
-
-
2
attr_reader :verifier
-
-
2
def aead_mode?
-
@aead_mode ||= new_cipher.authenticated?
-
end
-
-
2
def resolve_verifier
-
if aead_mode?
-
NullVerifier
-
else
-
MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "base64"
-
2
require "active_support/core_ext/object/blank"
-
2
require "active_support/security_utils"
-
2
require "active_support/messages/metadata"
-
2
require "active_support/messages/rotator"
-
-
2
module ActiveSupport
-
# +MessageVerifier+ makes it easy to generate and verify messages which are
-
# signed to prevent tampering.
-
#
-
# This is useful for cases like remember-me tokens and auto-unsubscribe links
-
# where the session store isn't suitable or available.
-
#
-
# Remember Me:
-
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
-
#
-
# In the authentication filter:
-
#
-
# id, time = @verifier.verify(cookies[:remember_me])
-
# if Time.now < time
-
# self.current_user = User.find(id)
-
# end
-
#
-
# By default it uses Marshal to serialize the message. If you want to use
-
# another serialization method, you can set the serializer in the options
-
# hash upon initialization:
-
#
-
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
-
#
-
# +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
-
# If you want to use a different hash algorithm, you can change it by providing
-
# +:digest+ key as an option while initializing the verifier:
-
#
-
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
-
#
-
# === Confining messages to a specific purpose
-
#
-
# By default any message can be used throughout your app. But they can also be
-
# confined to a specific +:purpose+.
-
#
-
# token = @verifier.generate("this is the chair", purpose: :login)
-
#
-
# Then that same purpose must be passed when verifying to get the data back out:
-
#
-
# @verifier.verified(token, purpose: :login) # => "this is the chair"
-
# @verifier.verified(token, purpose: :shipping) # => nil
-
# @verifier.verified(token) # => nil
-
#
-
# @verifier.verify(token, purpose: :login) # => "this is the chair"
-
# @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
-
# @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
-
#
-
# Likewise, if a message has no purpose it won't be returned when verifying with
-
# a specific purpose.
-
#
-
# token = @verifier.generate("the conversation is lively")
-
# @verifier.verified(token, purpose: :scare_tactics) # => nil
-
# @verifier.verified(token) # => "the conversation is lively"
-
#
-
# @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
-
# @verifier.verify(token) # => "the conversation is lively"
-
#
-
# === Making messages expire
-
#
-
# By default messages last forever and verifying one year from now will still
-
# return the original value. But messages can be set to expire at a given
-
# time with +:expires_in+ or +:expires_at+.
-
#
-
# @verifier.generate(parcel, expires_in: 1.month)
-
# @verifier.generate(doowad, expires_at: Time.now.end_of_year)
-
#
-
# Then the messages can be verified and returned up to the expire time.
-
# Thereafter, the +verified+ method returns +nil+ while +verify+ raises
-
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
-
#
-
# === Rotating keys
-
#
-
# MessageVerifier also supports rotating out old configurations by falling
-
# back to a stack of verifiers. Call +rotate+ to build and add a verifier to
-
# so either +verified+ or +verify+ will also try verifying with the fallback.
-
#
-
# By default any rotated verifiers use the values of the primary
-
# verifier unless specified otherwise.
-
#
-
# You'd give your verifier the new defaults:
-
#
-
# verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
-
#
-
# Then gradually rotate the old values out by adding them as fallbacks. Any message
-
# generated with the old values will then work until the rotation is removed.
-
#
-
# verifier.rotate old_secret # Fallback to an old secret instead of @secret.
-
# verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
-
# verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
-
#
-
# Though the above would most likely be combined into one rotation:
-
#
-
# verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
-
2
class MessageVerifier
-
2
prepend Messages::Rotator::Verifier
-
-
2
class InvalidSignature < StandardError; end
-
-
2
def initialize(secret, digest: nil, serializer: nil)
-
raise ArgumentError, "Secret should not be nil." unless secret
-
@secret = secret
-
@digest = digest || "SHA1"
-
@serializer = serializer || Marshal
-
end
-
-
# Checks if a signed message could have been generated by signing an object
-
# with the +MessageVerifier+'s secret.
-
#
-
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
-
# signed_message = verifier.generate 'a private message'
-
# verifier.valid_message?(signed_message) # => true
-
#
-
# tampered_message = signed_message.chop # editing the message invalidates the signature
-
# verifier.valid_message?(tampered_message) # => false
-
2
def valid_message?(signed_message)
-
return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
-
-
data, digest = signed_message.split("--")
-
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
-
end
-
-
# Decodes the signed message using the +MessageVerifier+'s secret.
-
#
-
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
-
#
-
# signed_message = verifier.generate 'a private message'
-
# verifier.verified(signed_message) # => 'a private message'
-
#
-
# Returns +nil+ if the message was not signed with the same secret.
-
#
-
# other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
-
# other_verifier.verified(signed_message) # => nil
-
#
-
# Returns +nil+ if the message is not Base64-encoded.
-
#
-
# invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
-
# verifier.verified(invalid_message) # => nil
-
#
-
# Raises any error raised while decoding the signed message.
-
#
-
# incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
-
# verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
-
2
def verified(signed_message, purpose: nil, **)
-
if valid_message?(signed_message)
-
begin
-
data = signed_message.split("--")[0]
-
message = Messages::Metadata.verify(decode(data), purpose)
-
@serializer.load(message) if message
-
rescue ArgumentError => argument_error
-
return if argument_error.message.include?("invalid base64")
-
raise
-
end
-
end
-
end
-
-
# Decodes the signed message using the +MessageVerifier+'s secret.
-
#
-
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
-
# signed_message = verifier.generate 'a private message'
-
#
-
# verifier.verify(signed_message) # => 'a private message'
-
#
-
# Raises +InvalidSignature+ if the message was not signed with the same
-
# secret or was not Base64-encoded.
-
#
-
# other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
-
# other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
-
2
def verify(*args, **options)
-
verified(*args, **options) || raise(InvalidSignature)
-
end
-
-
# Generates a signed message for the provided value.
-
#
-
# The message is signed with the +MessageVerifier+'s secret.
-
# Returns Base64-encoded message joined with the generated signature.
-
#
-
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
-
# verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
-
2
def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
-
data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
-
"#{data}--#{generate_digest(data)}"
-
end
-
-
2
private
-
2
def encode(data)
-
::Base64.strict_encode64(data)
-
end
-
-
2
def decode(data)
-
::Base64.strict_decode64(data)
-
end
-
-
2
def generate_digest(data)
-
require "openssl" unless defined?(OpenSSL)
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "time"
-
-
2
module ActiveSupport
-
2
module Messages #:nodoc:
-
2
class Metadata #:nodoc:
-
2
def initialize(message, expires_at = nil, purpose = nil)
-
@message, @purpose = message, purpose
-
@expires_at = expires_at.is_a?(String) ? Time.iso8601(expires_at) : expires_at
-
end
-
-
2
def as_json(options = {})
-
{ _rails: { message: @message, exp: @expires_at, pur: @purpose } }
-
end
-
-
2
class << self
-
2
def wrap(message, expires_at: nil, expires_in: nil, purpose: nil)
-
if expires_at || expires_in || purpose
-
JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
-
else
-
message
-
end
-
end
-
-
2
def verify(message, purpose)
-
extract_metadata(message).verify(purpose)
-
end
-
-
2
private
-
2
def pick_expiry(expires_at, expires_in)
-
if expires_at
-
expires_at.utc.iso8601(3)
-
elsif expires_in
-
Time.now.utc.advance(seconds: expires_in).iso8601(3)
-
end
-
end
-
-
2
def extract_metadata(message)
-
data = JSON.decode(message) rescue nil
-
-
if data.is_a?(Hash) && data.key?("_rails")
-
new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"])
-
else
-
new(message)
-
end
-
end
-
-
2
def encode(message)
-
::Base64.strict_encode64(message)
-
end
-
-
2
def decode(message)
-
::Base64.strict_decode64(message)
-
end
-
end
-
-
2
def verify(purpose)
-
@message if match?(purpose) && fresh?
-
end
-
-
2
private
-
2
def match?(purpose)
-
@purpose.to_s == purpose.to_s
-
end
-
-
2
def fresh?
-
@expires_at.nil? || Time.now.utc < @expires_at
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveSupport
-
2
module Messages
-
2
class RotationConfiguration # :nodoc:
-
2
attr_reader :signed, :encrypted
-
-
2
def initialize
-
@signed, @encrypted = [], []
-
end
-
-
2
def rotate(kind, *args, **options)
-
args << options unless options.empty?
-
case kind
-
when :signed
-
@signed << args
-
when :encrypted
-
@encrypted << args
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveSupport
-
2
module Messages
-
2
module Rotator # :nodoc:
-
2
def initialize(*secrets, on_rotation: nil, **options)
-
super(*secrets, **options)
-
-
@options = options
-
@rotations = []
-
@on_rotation = on_rotation
-
end
-
-
2
def rotate(*secrets, **options)
-
@rotations << build_rotation(*secrets, @options.merge(options))
-
end
-
-
2
module Encryptor
-
2
include Rotator
-
-
2
def decrypt_and_verify(*args, on_rotation: @on_rotation, **options)
-
super
-
rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
-
run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, **options) } || raise
-
end
-
-
2
private
-
2
def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
-
self.class.new(secret, sign_secret, **options)
-
end
-
end
-
-
2
module Verifier
-
2
include Rotator
-
-
2
def verified(*args, on_rotation: @on_rotation, **options)
-
super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, **options) }
-
end
-
-
2
private
-
2
def build_rotation(secret = @secret, options)
-
self.class.new(secret, **options)
-
end
-
end
-
-
2
private
-
2
def run_rotations(on_rotation)
-
@rotations.find do |rotation|
-
if message = yield(rotation) rescue next
-
on_rotation&.call
-
return message
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport #:nodoc:
-
23
module Multibyte
-
23
autoload :Chars, "active_support/multibyte/chars"
-
23
autoload :Unicode, "active_support/multibyte/unicode"
-
-
# The proxy class returned when calling mb_chars. You can use this accessor
-
# to configure your own proxy class so you can support other encodings. See
-
# the ActiveSupport::Multibyte::Chars implementation for an example how to
-
# do this.
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
23
def self.proxy_class=(klass)
-
@proxy_class = klass
-
end
-
-
# Returns the current proxy class.
-
23
def self.proxy_class
-
@proxy_class ||= ActiveSupport::Multibyte::Chars
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/json"
-
require "active_support/core_ext/string/access"
-
require "active_support/core_ext/string/behavior"
-
require "active_support/core_ext/symbol/starts_ends_with"
-
require "active_support/core_ext/module/delegation"
-
-
module ActiveSupport #:nodoc:
-
module Multibyte #:nodoc:
-
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
-
# String class without having extensive knowledge about the encoding. A
-
# Chars object accepts a string upon initialization and proxies String
-
# methods in an encoding safe manner. All the normal String methods are also
-
# implemented on the proxy.
-
#
-
# String methods are proxied through the Chars object, and can be accessed
-
# through the +mb_chars+ method. Methods which would normally return a
-
# String object now return a Chars object so methods can be chained.
-
#
-
# 'The Perfect String '.mb_chars.downcase.strip
-
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
-
#
-
# Chars objects are perfectly interchangeable with String objects as long as
-
# no explicit class checks are made. If certain methods do explicitly check
-
# the class, call +to_s+ before you pass chars objects to them.
-
#
-
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
-
#
-
# The default Chars implementation assumes that the encoding of the string
-
# is UTF-8, if you want to handle different encodings you can write your own
-
# multibyte string handler and configure it through
-
# ActiveSupport::Multibyte.proxy_class.
-
#
-
# class CharsForUTF32
-
# def size
-
# @wrapped_string.size / 4
-
# end
-
#
-
# def self.accepts?(string)
-
# string.length % 4 == 0
-
# end
-
# end
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
class Chars
-
include Comparable
-
attr_reader :wrapped_string
-
alias to_s wrapped_string
-
alias to_str wrapped_string
-
-
delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
-
-
# Creates a new Chars instance by wrapping _string_.
-
def initialize(string)
-
@wrapped_string = string
-
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
-
end
-
-
# Forward all undefined methods to the wrapped string.
-
def method_missing(method, *args, &block)
-
result = @wrapped_string.__send__(method, *args, &block)
-
if method.end_with?("!")
-
self if result
-
else
-
result.kind_of?(String) ? chars(result) : result
-
end
-
end
-
-
# Returns +true+ if _obj_ responds to the given method. Private methods
-
# are included in the search only if the optional second parameter
-
# evaluates to +true+.
-
def respond_to_missing?(method, include_private)
-
@wrapped_string.respond_to?(method, include_private)
-
end
-
-
# Returns +true+ when the proxy class can handle the string. Returns
-
# +false+ otherwise.
-
def self.consumes?(string)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be
-
removed from Rails 6.1. Use string.is_utf8? instead.
-
MSG
-
-
string.encoding == Encoding::UTF_8
-
end
-
-
# Works just like <tt>String#split</tt>, with the exception that the items
-
# in the resulting list are Chars instances instead of String. This makes
-
# chaining methods easier.
-
#
-
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
-
def split(*args)
-
@wrapped_string.split(*args).map { |i| self.class.new(i) }
-
end
-
-
# Works like <tt>String#slice!</tt>, but returns an instance of
-
# Chars, or +nil+ if the string was not modified. The string will not be
-
# modified if the range given is out of bounds
-
#
-
# string = 'Welcome'
-
# string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
-
# string # => 'Welome'
-
# string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
-
# string # => 'me'
-
def slice!(*args)
-
string_sliced = @wrapped_string.slice!(*args)
-
if string_sliced
-
chars(string_sliced)
-
end
-
end
-
-
# Reverses all characters in the string.
-
#
-
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
-
def reverse
-
chars(@wrapped_string.scan(/\X/).reverse.join)
-
end
-
-
# Limits the byte size of the string to a number of bytes without breaking
-
# characters. Usable when the storage for a string is limited for some
-
# reason.
-
#
-
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
-
def limit(limit)
-
chars(@wrapped_string.truncate_bytes(limit, omission: nil))
-
end
-
-
# Capitalizes the first letter of every word, when possible.
-
#
-
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
-
# "日本語".mb_chars.titleize.to_s # => "日本語"
-
def titleize
-
chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
-
end
-
alias_method :titlecase, :titleize
-
-
# Returns the KC normalization of the string by default. NFKC is
-
# considered the best normalization form for passing strings to databases
-
# and validations.
-
#
-
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
-
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
-
# ActiveSupport::Multibyte::Unicode.default_normalization_form
-
def normalize(form = nil)
-
form ||= Unicode.default_normalization_form
-
-
# See https://www.unicode.org/reports/tr15, Table 1
-
if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form]
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
-
removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead.
-
MSG
-
-
send(:unicode_normalize, alias_form)
-
else
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
-
removed from Rails 6.1. Use #unicode_normalize instead.
-
MSG
-
-
raise ArgumentError, "#{form} is not a valid normalization variant", caller
-
end
-
end
-
-
# Performs canonical decomposition on all the characters.
-
#
-
# 'é'.length # => 2
-
# 'é'.mb_chars.decompose.to_s.length # => 3
-
def decompose
-
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
-
end
-
-
# Performs composition on all the characters.
-
#
-
# 'é'.length # => 3
-
# 'é'.mb_chars.compose.to_s.length # => 2
-
def compose
-
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
-
end
-
-
# Returns the number of grapheme clusters in the string.
-
#
-
# 'क्षि'.mb_chars.length # => 4
-
# 'क्षि'.mb_chars.grapheme_length # => 3
-
def grapheme_length
-
@wrapped_string.scan(/\X/).length
-
end
-
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
-
# resulting in a valid UTF-8 string.
-
#
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
-
# encoding is entirely CP1252 or ISO-8859-1.
-
def tidy_bytes(force = false)
-
chars(Unicode.tidy_bytes(@wrapped_string, force))
-
end
-
-
def as_json(options = nil) #:nodoc:
-
to_s.as_json(options)
-
end
-
-
%w(reverse tidy_bytes).each do |method|
-
define_method("#{method}!") do |*args|
-
@wrapped_string = send(method, *args).to_s
-
self
-
end
-
end
-
-
private
-
def chars(string)
-
self.class.new(string)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveSupport
-
2
module Multibyte
-
2
module Unicode
-
2
extend self
-
-
# A list of all available normalization forms.
-
# See https://www.unicode.org/reports/tr15/tr15-29.html for more
-
# information about normalization.
-
2
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
-
-
2
NORMALIZATION_FORM_ALIASES = { # :nodoc:
-
c: :nfc,
-
d: :nfd,
-
kc: :nfkc,
-
kd: :nfkd
-
}
-
-
# The Unicode version that is supported by the implementation
-
2
UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
-
-
# The default normalization used for operations that require
-
# normalization. It can be set to any of the normalizations
-
# in NORMALIZATION_FORMS.
-
#
-
# ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
-
2
attr_accessor :default_normalization_form
-
2
@default_normalization_form = :kc
-
-
# Unpack the string at grapheme boundaries. Returns a list of character
-
# lists.
-
#
-
# Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
-
# Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
-
2
def unpack_graphemes(string)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
-
removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
-
MSG
-
-
string.scan(/\X/).map(&:codepoints)
-
end
-
-
# Reverse operation of unpack_graphemes.
-
#
-
# Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
-
2
def pack_graphemes(unpacked)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
-
removed from Rails 6.1. Use array.flatten.pack("U*") instead.
-
MSG
-
-
unpacked.flatten.pack("U*")
-
end
-
-
# Decompose composed characters to the decomposed form.
-
2
def decompose(type, codepoints)
-
if type == :compatibility
-
codepoints.pack("U*").unicode_normalize(:nfkd).codepoints
-
else
-
codepoints.pack("U*").unicode_normalize(:nfd).codepoints
-
end
-
end
-
-
# Compose decomposed characters to the composed form.
-
2
def compose(codepoints)
-
codepoints.pack("U*").unicode_normalize(:nfc).codepoints
-
end
-
-
# Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
-
2
if !defined?(Rubinius)
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
-
# resulting in a valid UTF-8 string.
-
#
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
-
# encoding is entirely CP1252 or ISO-8859-1.
-
2
def tidy_bytes(string, force = false)
-
return string if string.empty? || string.ascii_only?
-
return recode_windows1252_chars(string) if force
-
string.scrub { |bad| recode_windows1252_chars(bad) }
-
end
-
else
-
def tidy_bytes(string, force = false)
-
return string if string.empty?
-
return recode_windows1252_chars(string) if force
-
-
# We can't transcode to the same format, so we choose a nearly-identical encoding.
-
# We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
-
# CP1252 when we get errors. The final string will be 'converted' back to UTF-8
-
# before returning.
-
reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
-
-
source = string.dup
-
out = "".force_encoding(Encoding::UTF_16LE)
-
-
loop do
-
reader.primitive_convert(source, out)
-
_, _, _, error_bytes, _ = reader.primitive_errinfo
-
break if error_bytes.nil?
-
out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
-
end
-
-
reader.finish
-
-
out.encode!(Encoding::UTF_8)
-
end
-
end
-
-
# Returns the KC normalization of the string by default. NFKC is
-
# considered the best normalization form for passing strings to databases
-
# and validations.
-
#
-
# * <tt>string</tt> - The string to perform normalization on.
-
# * <tt>form</tt> - The form you want to normalize in. Should be one of
-
# the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
-
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
-
2
def normalize(string, form = nil)
-
form ||= @default_normalization_form
-
-
# See https://www.unicode.org/reports/tr15, Table 1
-
if alias_form = NORMALIZATION_FORM_ALIASES[form]
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
-
removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
-
MSG
-
-
string.unicode_normalize(alias_form)
-
else
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
-
removed from Rails 6.1. Use String#unicode_normalize instead.
-
MSG
-
-
raise ArgumentError, "#{form} is not a valid normalization variant", caller
-
end
-
end
-
-
2
%w(downcase upcase swapcase).each do |method|
-
6
define_method(method) do |string|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveSupport::Multibyte::Unicode##{method} is deprecated and
-
will be removed from Rails 6.1. Use String methods directly.
-
MSG
-
-
string.send(method)
-
end
-
end
-
-
2
private
-
2
def recode_windows1252_chars(string)
-
string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/notifications/instrumenter"
-
24
require "active_support/notifications/fanout"
-
24
require "active_support/per_thread_registry"
-
-
24
module ActiveSupport
-
# = Notifications
-
#
-
# <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
-
# Ruby.
-
#
-
# == Instrumenters
-
#
-
# To instrument an event you just need to do:
-
#
-
# ActiveSupport::Notifications.instrument('render', extra: :information) do
-
# render plain: 'Foo'
-
# end
-
#
-
# That first executes the block and then notifies all subscribers once done.
-
#
-
# In the example above +render+ is the name of the event, and the rest is called
-
# the _payload_. The payload is a mechanism that allows instrumenters to pass
-
# extra information to subscribers. Payloads consist of a hash whose contents
-
# are arbitrary and generally depend on the event.
-
#
-
# == Subscribers
-
#
-
# You can consume those events and the information they provide by registering
-
# a subscriber.
-
#
-
# ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
-
# name # => String, name of the event (such as 'render' from above)
-
# start # => Time, when the instrumented block started execution
-
# finish # => Time, when the instrumented block ended execution
-
# id # => String, unique ID for the instrumenter that fired the event
-
# payload # => Hash, the payload
-
# end
-
#
-
# Here, the +start+ and +finish+ values represent wall-clock time. If you are
-
# concerned about accuracy, you can register a monotonic subscriber.
-
#
-
# ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
-
# name # => String, name of the event (such as 'render' from above)
-
# start # => Monotonic time, when the instrumented block started execution
-
# finish # => Monotonic time, when the instrumented block ended execution
-
# id # => String, unique ID for the instrumenter that fired the event
-
# payload # => Hash, the payload
-
# end
-
#
-
# The +start+ and +finish+ values above represent monotonic time.
-
#
-
# For instance, let's store all "render" events in an array:
-
#
-
# events = []
-
#
-
# ActiveSupport::Notifications.subscribe('render') do |*args|
-
# events << ActiveSupport::Notifications::Event.new(*args)
-
# end
-
#
-
# That code returns right away, you are just subscribing to "render" events.
-
# The block is saved and will be called whenever someone instruments "render":
-
#
-
# ActiveSupport::Notifications.instrument('render', extra: :information) do
-
# render plain: 'Foo'
-
# end
-
#
-
# event = events.first
-
# event.name # => "render"
-
# event.duration # => 10 (in milliseconds)
-
# event.payload # => { extra: :information }
-
#
-
# The block in the <tt>subscribe</tt> call gets the name of the event, start
-
# timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
-
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
-
# that order.
-
#
-
# If an exception happens during that particular instrumentation the payload will
-
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
-
# the name of the exception class, and the exception message.
-
# The <tt>:exception_object</tt> key of the payload will have the exception
-
# itself as the value:
-
#
-
# event.payload[:exception] # => ["ArgumentError", "Invalid value"]
-
# event.payload[:exception_object] # => #<ArgumentError: Invalid value>
-
#
-
# As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
-
# is able to take the arguments as they come and provide an object-oriented
-
# interface to that data.
-
#
-
# It is also possible to pass an object which responds to <tt>call</tt> method
-
# as the second parameter to the <tt>subscribe</tt> method instead of a block:
-
#
-
# module ActionController
-
# class PageRequest
-
# def call(name, started, finished, unique_id, payload)
-
# Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
-
# end
-
# end
-
# end
-
#
-
# ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
-
#
-
# resulting in the following output within the logs including a hash with the payload:
-
#
-
# notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
-
# controller: "Devise::SessionsController",
-
# action: "new",
-
# params: {"action"=>"new", "controller"=>"devise/sessions"},
-
# format: :html,
-
# method: "GET",
-
# path: "/login/sign_in",
-
# status: 200,
-
# view_runtime: 279.3080806732178,
-
# db_runtime: 40.053
-
# }
-
#
-
# You can also subscribe to all events whose name matches a certain regexp:
-
#
-
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
-
# ...
-
# end
-
#
-
# and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
-
# to all events.
-
#
-
# == Temporary Subscriptions
-
#
-
# Sometimes you do not want to subscribe to an event for the entire life of
-
# the application. There are two ways to unsubscribe.
-
#
-
# WARNING: The instrumentation framework is designed for long-running subscribers,
-
# use this feature sparingly because it wipes some internal caches and that has
-
# a negative impact on performance.
-
#
-
# === Subscribe While a Block Runs
-
#
-
# You can subscribe to some event temporarily while some block runs. For
-
# example, in
-
#
-
# callback = lambda {|*args| ... }
-
# ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
-
# ...
-
# end
-
#
-
# the callback will be called for all "sql.active_record" events instrumented
-
# during the execution of the block. The callback is unsubscribed automatically
-
# after that.
-
#
-
# To record +started+ and +finished+ values with monotonic time,
-
# specify the optional <tt>:monotonic</tt> option to the
-
# <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set
-
# to +false+ by default.
-
#
-
# callback = lambda {|name, started, finished, unique_id, payload| ... }
-
# ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do
-
# ...
-
# end
-
#
-
# === Manual Unsubscription
-
#
-
# The +subscribe+ method returns a subscriber object:
-
#
-
# subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
-
# ...
-
# end
-
#
-
# To prevent that block from being called anymore, just unsubscribe passing
-
# that reference:
-
#
-
# ActiveSupport::Notifications.unsubscribe(subscriber)
-
#
-
# You can also unsubscribe by passing the name of the subscriber object. Note
-
# that this will unsubscribe all subscriptions with the given name:
-
#
-
# ActiveSupport::Notifications.unsubscribe("render")
-
#
-
# Subscribers using a regexp or other pattern-matching object will remain subscribed
-
# to all events that match their original pattern, unless those events match a string
-
# passed to `unsubscribe`:
-
#
-
# subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
-
# ActiveSupport::Notifications.unsubscribe('render_template.action_view')
-
# subscriber.matches?('render_template.action_view') # => false
-
# subscriber.matches?('render_partial.action_view') # => true
-
#
-
# == Default Queue
-
#
-
# Notifications ships with a queue implementation that consumes and publishes events
-
# to all log subscribers. You can use any queue implementation you want.
-
#
-
24
module Notifications
-
24
class << self
-
24
attr_accessor :notifier
-
-
24
def publish(name, *args)
-
notifier.publish(name, *args)
-
end
-
-
24
def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
end
-
-
# Subscribe to a given event name with the passed +block+.
-
#
-
# You can subscribe to events by passing a String to match exact event
-
# names, or by passing a Regexp to match all events that match a pattern.
-
#
-
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
-
# @event = ActiveSupport::Notifications::Event.new(*args)
-
# end
-
#
-
# The +block+ will receive five parameters with information about the event:
-
#
-
# ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
-
# name # => String, name of the event (such as 'render' from above)
-
# start # => Time, when the instrumented block started execution
-
# finish # => Time, when the instrumented block ended execution
-
# id # => String, unique ID for the instrumenter that fired the event
-
# payload # => Hash, the payload
-
# end
-
#
-
# If the block passed to the method only takes one parameter,
-
# it will yield an event object to the block:
-
#
-
# ActiveSupport::Notifications.subscribe(/render/) do |event|
-
# @event = event
-
# end
-
24
def subscribe(pattern = nil, callback = nil, &block)
-
2
notifier.subscribe(pattern, callback, monotonic: false, &block)
-
end
-
-
24
def monotonic_subscribe(pattern = nil, callback = nil, &block)
-
notifier.subscribe(pattern, callback, monotonic: true, &block)
-
end
-
-
24
def subscribed(callback, pattern = nil, monotonic: false, &block)
-
subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
-
yield
-
ensure
-
unsubscribe(subscriber)
-
end
-
-
24
def unsubscribe(subscriber_or_name)
-
notifier.unsubscribe(subscriber_or_name)
-
end
-
-
24
def instrumenter
-
InstrumentationRegistry.instance.instrumenter_for(notifier)
-
end
-
end
-
-
# This class is a registry which holds all of the +Instrumenter+ objects
-
# in a particular thread local. To access the +Instrumenter+ object for a
-
# particular +notifier+, you can call the following method:
-
#
-
# InstrumentationRegistry.instrumenter_for(notifier)
-
#
-
# The instrumenters for multiple notifiers are held in a single instance of
-
# this class.
-
24
class InstrumentationRegistry # :nodoc:
-
24
extend ActiveSupport::PerThreadRegistry
-
-
24
def initialize
-
@registry = {}
-
end
-
-
24
def instrumenter_for(notifier)
-
@registry[notifier] ||= Instrumenter.new(notifier)
-
end
-
end
-
-
24
self.notifier = Fanout.new
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "mutex_m"
-
24
require "concurrent/map"
-
24
require "set"
-
24
require "active_support/core_ext/object/try"
-
-
24
module ActiveSupport
-
24
module Notifications
-
# This is a default queue implementation that ships with Notifications.
-
# It just pushes events to all registered log subscribers.
-
#
-
# This class is thread safe. All methods are reentrant.
-
24
class Fanout
-
24
include Mutex_m
-
-
24
def initialize
-
26
@string_subscribers = Hash.new { |h, k| h[k] = [] }
-
24
@other_subscribers = []
-
24
@listeners_for = Concurrent::Map.new
-
24
super
-
end
-
-
24
def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
-
2
subscriber = Subscribers.new(pattern, callable || block, monotonic)
-
2
synchronize do
-
2
if String === pattern
-
2
@string_subscribers[pattern] << subscriber
-
2
@listeners_for.delete(pattern)
-
else
-
@other_subscribers << subscriber
-
@listeners_for.clear
-
end
-
end
-
2
subscriber
-
end
-
-
24
def unsubscribe(subscriber_or_name)
-
synchronize do
-
case subscriber_or_name
-
when String
-
@string_subscribers[subscriber_or_name].clear
-
@listeners_for.delete(subscriber_or_name)
-
@other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
-
else
-
pattern = subscriber_or_name.try(:pattern)
-
if String === pattern
-
@string_subscribers[pattern].delete(subscriber_or_name)
-
@listeners_for.delete(pattern)
-
else
-
@other_subscribers.delete(subscriber_or_name)
-
@listeners_for.clear
-
end
-
end
-
end
-
end
-
-
24
def start(name, id, payload)
-
listeners_for(name).each { |s| s.start(name, id, payload) }
-
end
-
-
24
def finish(name, id, payload, listeners = listeners_for(name))
-
listeners.each { |s| s.finish(name, id, payload) }
-
end
-
-
24
def publish(name, *args)
-
listeners_for(name).each { |s| s.publish(name, *args) }
-
end
-
-
24
def listeners_for(name)
-
# this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
-
@listeners_for[name] || synchronize do
-
# use synchronisation when accessing @subscribers
-
@listeners_for[name] ||=
-
@string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
-
end
-
end
-
-
24
def listening?(name)
-
listeners_for(name).any?
-
end
-
-
# This is a sync queue, so there is no waiting.
-
24
def wait
-
end
-
-
24
module Subscribers # :nodoc:
-
24
def self.new(pattern, listener, monotonic)
-
2
subscriber_class = monotonic ? MonotonicTimed : Timed
-
-
2
if listener.respond_to?(:start) && listener.respond_to?(:finish)
-
2
subscriber_class = Evented
-
else
-
# Doing all this to detect a block like `proc { |x| }` vs
-
# `proc { |*x| }` or `proc { |**x| }`
-
if listener.respond_to?(:parameters)
-
params = listener.parameters
-
if params.length == 1 && params.first.first == :opt
-
subscriber_class = EventObject
-
end
-
end
-
end
-
-
2
wrap_all pattern, subscriber_class.new(pattern, listener)
-
end
-
-
24
def self.wrap_all(pattern, subscriber)
-
2
unless pattern
-
AllMessages.new(subscriber)
-
else
-
2
subscriber
-
end
-
end
-
-
24
class Matcher #:nodoc:
-
24
attr_reader :pattern, :exclusions
-
-
24
def self.wrap(pattern)
-
2
return pattern if String === pattern
-
new(pattern)
-
end
-
-
24
def initialize(pattern)
-
@pattern = pattern
-
@exclusions = Set.new
-
end
-
-
24
def unsubscribe!(name)
-
exclusions << -name if pattern === name
-
end
-
-
24
def ===(name)
-
pattern === name && !exclusions.include?(name)
-
end
-
end
-
-
24
class Evented #:nodoc:
-
24
attr_reader :pattern
-
-
24
def initialize(pattern, delegate)
-
2
@pattern = Matcher.wrap(pattern)
-
2
@delegate = delegate
-
2
@can_publish = delegate.respond_to?(:publish)
-
end
-
-
24
def publish(name, *args)
-
if @can_publish
-
@delegate.publish name, *args
-
end
-
end
-
-
24
def start(name, id, payload)
-
@delegate.start name, id, payload
-
end
-
-
24
def finish(name, id, payload)
-
@delegate.finish name, id, payload
-
end
-
-
24
def subscribed_to?(name)
-
pattern === name
-
end
-
-
24
def matches?(name)
-
pattern && pattern === name
-
end
-
-
24
def unsubscribe!(name)
-
pattern.unsubscribe!(name)
-
end
-
end
-
-
24
class Timed < Evented # :nodoc:
-
24
def publish(name, *args)
-
@delegate.call name, *args
-
end
-
-
24
def start(name, id, payload)
-
timestack = Thread.current[:_timestack] ||= []
-
timestack.push Time.now
-
end
-
-
24
def finish(name, id, payload)
-
timestack = Thread.current[:_timestack]
-
started = timestack.pop
-
@delegate.call(name, started, Time.now, id, payload)
-
end
-
end
-
-
24
class MonotonicTimed < Evented # :nodoc:
-
24
def publish(name, *args)
-
@delegate.call name, *args
-
end
-
-
24
def start(name, id, payload)
-
timestack = Thread.current[:_timestack_monotonic] ||= []
-
timestack.push Concurrent.monotonic_time
-
end
-
-
24
def finish(name, id, payload)
-
timestack = Thread.current[:_timestack_monotonic]
-
started = timestack.pop
-
@delegate.call(name, started, Concurrent.monotonic_time, id, payload)
-
end
-
end
-
-
24
class EventObject < Evented
-
24
def start(name, id, payload)
-
stack = Thread.current[:_event_stack] ||= []
-
event = build_event name, id, payload
-
event.start!
-
stack.push event
-
end
-
-
24
def finish(name, id, payload)
-
stack = Thread.current[:_event_stack]
-
event = stack.pop
-
event.payload = payload
-
event.finish!
-
@delegate.call event
-
end
-
-
24
private
-
24
def build_event(name, id, payload)
-
ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
-
end
-
end
-
-
24
class AllMessages # :nodoc:
-
24
def initialize(delegate)
-
@delegate = delegate
-
end
-
-
24
def start(name, id, payload)
-
@delegate.start name, id, payload
-
end
-
-
24
def finish(name, id, payload)
-
@delegate.finish name, id, payload
-
end
-
-
24
def publish(name, *args)
-
@delegate.publish name, *args
-
end
-
-
24
def subscribed_to?(name)
-
true
-
end
-
-
24
def unsubscribe!(*)
-
false
-
end
-
-
24
alias :matches? :===
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "securerandom"
-
-
24
module ActiveSupport
-
24
module Notifications
-
# Instrumenters are stored in a thread local.
-
24
class Instrumenter
-
24
attr_reader :id
-
-
24
def initialize(notifier)
-
@id = unique_id
-
@notifier = notifier
-
end
-
-
# Given a block, instrument it by measuring the time taken to execute
-
# and publish it. Without a block, simply send a message via the
-
# notifier. Notice that events get sent even if an error occurs in the
-
# passed-in block.
-
24
def instrument(name, payload = {})
-
# some of the listeners might have state
-
listeners_state = start name, payload
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
raise e
-
ensure
-
finish_with_state listeners_state, name, payload
-
end
-
end
-
-
# Send a start notification with +name+ and +payload+.
-
24
def start(name, payload)
-
@notifier.start name, @id, payload
-
end
-
-
# Send a finish notification with +name+ and +payload+.
-
24
def finish(name, payload)
-
@notifier.finish name, @id, payload
-
end
-
-
24
def finish_with_state(listeners_state, name, payload)
-
@notifier.finish name, @id, payload, listeners_state
-
end
-
-
24
private
-
24
def unique_id
-
SecureRandom.hex(10)
-
end
-
end
-
-
24
class Event
-
24
attr_reader :name, :time, :end, :transaction_id, :children
-
24
attr_accessor :payload
-
-
24
def self.clock_gettime_supported? # :nodoc:
-
24
defined?(Process::CLOCK_THREAD_CPUTIME_ID) &&
-
!Gem.win_platform? &&
-
!RUBY_PLATFORM.match?(/solaris/i)
-
end
-
24
private_class_method :clock_gettime_supported?
-
-
24
def initialize(name, start, ending, transaction_id, payload)
-
@name = name
-
@payload = payload.dup
-
@time = start
-
@transaction_id = transaction_id
-
@end = ending
-
@children = []
-
@cpu_time_start = 0
-
@cpu_time_finish = 0
-
@allocation_count_start = 0
-
@allocation_count_finish = 0
-
end
-
-
# Record information at the time this event starts
-
24
def start!
-
@time = now
-
@cpu_time_start = now_cpu
-
@allocation_count_start = now_allocations
-
end
-
-
# Record information at the time this event finishes
-
24
def finish!
-
@cpu_time_finish = now_cpu
-
@end = now
-
@allocation_count_finish = now_allocations
-
end
-
-
24
def end=(ending)
-
ActiveSupport::Deprecation.deprecation_warning(:end=, :finish!)
-
@end = ending
-
end
-
-
# Returns the CPU time (in milliseconds) passed since the call to
-
# +start!+ and the call to +finish!+
-
24
def cpu_time
-
(@cpu_time_finish - @cpu_time_start) * 1000
-
end
-
-
# Returns the idle time time (in milliseconds) passed since the call to
-
# +start!+ and the call to +finish!+
-
24
def idle_time
-
duration - cpu_time
-
end
-
-
# Returns the number of allocations made since the call to +start!+ and
-
# the call to +finish!+
-
24
def allocations
-
@allocation_count_finish - @allocation_count_start
-
end
-
-
# Returns the difference in milliseconds between when the execution of the
-
# event started and when it ended.
-
#
-
# ActiveSupport::Notifications.subscribe('wait') do |*args|
-
# @event = ActiveSupport::Notifications::Event.new(*args)
-
# end
-
#
-
# ActiveSupport::Notifications.instrument('wait') do
-
# sleep 1
-
# end
-
#
-
# @event.duration # => 1000.138
-
24
def duration
-
1000.0 * (self.end - time)
-
end
-
-
24
def <<(event)
-
@children << event
-
end
-
-
24
def parent_of?(event)
-
@children.include? event
-
end
-
-
24
private
-
24
def now
-
Concurrent.monotonic_time
-
end
-
-
24
if clock_gettime_supported?
-
24
def now_cpu
-
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
-
end
-
else
-
def now_cpu
-
0
-
end
-
end
-
-
24
if defined?(JRUBY_VERSION)
-
def now_allocations
-
0
-
end
-
else
-
24
def now_allocations
-
GC.stat :total_allocated_objects
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module ActiveSupport
-
1
module NumberHelper
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :NumberConverter
-
1
autoload :RoundingHelper
-
1
autoload :NumberToRoundedConverter
-
1
autoload :NumberToDelimitedConverter
-
1
autoload :NumberToHumanConverter
-
1
autoload :NumberToHumanSizeConverter
-
1
autoload :NumberToPhoneConverter
-
1
autoload :NumberToCurrencyConverter
-
1
autoload :NumberToPercentageConverter
-
end
-
-
1
extend self
-
-
# Formats a +number+ into a phone number (US by default e.g., (555)
-
# 123-9876). You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:area_code</tt> - Adds parentheses around the area code.
-
# * <tt>:delimiter</tt> - Specifies the delimiter to use
-
# (defaults to "-").
-
# * <tt>:extension</tt> - Specifies an extension to add to the
-
# end of the generated number.
-
# * <tt>:country_code</tt> - Sets the country code for the phone
-
# number.
-
# * <tt>:pattern</tt> - Specifies how the number is divided into three
-
# groups with the custom regexp to override the default format.
-
# ==== Examples
-
#
-
# number_to_phone(5551234) # => "555-1234"
-
# number_to_phone('5551234') # => "555-1234"
-
# number_to_phone(1235551234) # => "123-555-1234"
-
# number_to_phone(1235551234, area_code: true) # => "(123) 555-1234"
-
# number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234"
-
# number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
-
# number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234"
-
# number_to_phone('123a456') # => "123a456"
-
#
-
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
-
# # => "+1.123.555.1234 x 1343"
-
#
-
# number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
-
# # => "(755) 6123-4567"
-
# number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
-
# # => "133-1234-5678"
-
1
def number_to_phone(number, options = {})
-
NumberToPhoneConverter.convert(number, options)
-
end
-
-
# Formats a +number+ into a currency string (e.g., $13.65). You
-
# can customize the format in the +options+ hash.
-
#
-
# The currency unit and number formatting of the current locale will be used
-
# unless otherwise specified in the provided options. No currency conversion
-
# is performed. If the user is given a way to change their locale, they will
-
# also be able to change the relative value of the currency displayed with
-
# this helper. If your application will ever support multiple locales, you
-
# may want to specify a constant <tt>:locale</tt> option or consider
-
# using a library capable of currency conversion.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the level of precision (defaults
-
# to 2).
-
# * <tt>:round_mode</tt> - Determine how rounding is performed
-
# (defaults to :default. See BigDecimal::mode)
-
# * <tt>:unit</tt> - Sets the denomination of the currency
-
# (defaults to "$").
-
# * <tt>:separator</tt> - Sets the separator between the units
-
# (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:format</tt> - Sets the format for non-negative numbers
-
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
-
# currency, and <tt>%n</tt> for the number.
-
# * <tt>:negative_format</tt> - Sets the format for negative
-
# numbers (defaults to prepending a hyphen to the formatted
-
# number given by <tt>:format</tt>). Accepts the same fields
-
# than <tt>:format</tt>, except <tt>%n</tt> is here the
-
# absolute value of the number.
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
#
-
# ==== Examples
-
#
-
# number_to_currency(1234567890.50) # => "$1,234,567,890.50"
-
# number_to_currency(1234567890.506) # => "$1,234,567,890.51"
-
# number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506"
-
# number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
-
# number_to_currency('123a456') # => "$123a456"
-
#
-
# number_to_currency("123a456", raise: true) # => InvalidNumberError
-
#
-
# number_to_currency(-0.456789, precision: 0)
-
# # => "$0"
-
# number_to_currency(-1234567890.50, negative_format: '(%u%n)')
-
# # => "($1,234,567,890.50)"
-
# number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '')
-
# # => "£1234567890,50"
-
# number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u')
-
# # => "1234567890,50 £"
-
# number_to_currency(1234567890.50, strip_insignificant_zeros: true)
-
# # => "$1,234,567,890.5"
-
# number_to_currency(1234567890.50, precision: 0, round_mode: :up)
-
# # => "$1,234,567,891"
-
1
def number_to_currency(number, options = {})
-
NumberToCurrencyConverter.convert(number, options)
-
end
-
-
# Formats a +number+ as a percentage string (e.g., 65%). You can
-
# customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3). Keeps the number's precision if +nil+.
-
# * <tt>:round_mode</tt> - Determine how rounding is performed
-
# (defaults to :default. See BigDecimal::mode)
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:format</tt> - Specifies the format of the percentage
-
# string The number field is <tt>%n</tt> (defaults to "%n%").
-
#
-
# ==== Examples
-
#
-
# number_to_percentage(100) # => "100.000%"
-
# number_to_percentage('98') # => "98.000%"
-
# number_to_percentage(100, precision: 0) # => "100%"
-
# number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
-
# number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
-
# number_to_percentage(1000, locale: :fr) # => "1000,000%"
-
# number_to_percentage(1000, precision: nil) # => "1000%"
-
# number_to_percentage('98a') # => "98a%"
-
# number_to_percentage(100, format: '%n %') # => "100.000 %"
-
# number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%"
-
1
def number_to_percentage(number, options = {})
-
NumberToPercentageConverter.convert(number, options)
-
end
-
-
# Formats a +number+ with grouped thousands using +delimiter+
-
# (e.g., 12,324). You can customize the format in the +options+
-
# hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
-
# deriving the placement of delimiter. Helpful when using currency formats
-
# like INR.
-
#
-
# ==== Examples
-
#
-
# number_to_delimited(12345678) # => "12,345,678"
-
# number_to_delimited('123456') # => "123,456"
-
# number_to_delimited(12345678.05) # => "12,345,678.05"
-
# number_to_delimited(12345678, delimiter: '.') # => "12.345.678"
-
# number_to_delimited(12345678, delimiter: ',') # => "12,345,678"
-
# number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05"
-
# number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05"
-
# number_to_delimited('112a') # => "112a"
-
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
-
# # => "98 765 432,98"
-
# number_to_delimited("123456.78",
-
# delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)
-
# # => "1,23,456.78"
-
1
def number_to_delimited(number, options = {})
-
NumberToDelimitedConverter.convert(number, options)
-
end
-
-
# Formats a +number+ with the specified level of
-
# <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
-
# +:significant+ is +false+, and 5 if +:significant+ is +true+).
-
# You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3). Keeps the number's precision if +nil+.
-
# * <tt>:round_mode</tt> - Determine how rounding is performed
-
# (defaults to :default. See BigDecimal::mode)
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
#
-
# ==== Examples
-
#
-
# number_to_rounded(111.2345) # => "111.235"
-
# number_to_rounded(111.2345, precision: 2) # => "111.23"
-
# number_to_rounded(13, precision: 5) # => "13.00000"
-
# number_to_rounded(389.32314, precision: 0) # => "389"
-
# number_to_rounded(111.2345, significant: true) # => "111"
-
# number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
-
# number_to_rounded(13, precision: 5, significant: true) # => "13.000"
-
# number_to_rounded(13, precision: nil) # => "13"
-
# number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390"
-
# number_to_rounded(111.234, locale: :fr) # => "111,234"
-
#
-
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
-
# # => "13"
-
#
-
# number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3"
-
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
-
# # => "1.111,23"
-
1
def number_to_rounded(number, options = {})
-
NumberToRoundedConverter.convert(number, options)
-
end
-
-
# Formats the bytes in +number+ into a more understandable
-
# representation (e.g., giving it 1500 yields 1.46 KB). This
-
# method is useful for reporting file sizes to users. You can
-
# customize the format in the +options+ hash.
-
#
-
# See <tt>number_to_human</tt> if you want to pretty-print a
-
# generic number.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:round_mode</tt> - Determine how rounding is performed
-
# (defaults to :default. See BigDecimal::mode)
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
#
-
# ==== Examples
-
#
-
# number_to_human_size(123) # => "123 Bytes"
-
# number_to_human_size(1234) # => "1.21 KB"
-
# number_to_human_size(12345) # => "12.1 KB"
-
# number_to_human_size(1234567) # => "1.18 MB"
-
# number_to_human_size(1234567890) # => "1.15 GB"
-
# number_to_human_size(1234567890123) # => "1.12 TB"
-
# number_to_human_size(1234567890123456) # => "1.1 PB"
-
# number_to_human_size(1234567890123456789) # => "1.07 EB"
-
# number_to_human_size(1234567, precision: 2) # => "1.2 MB"
-
# number_to_human_size(483989, precision: 2) # => "470 KB"
-
# number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB"
-
# number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
-
# number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
-
# number_to_human_size(524288000, precision: 5) # => "500 MB"
-
1
def number_to_human_size(number, options = {})
-
NumberToHumanSizeConverter.convert(number, options)
-
end
-
-
# Pretty prints (formats and approximates) a number in a way it
-
# is more readable by humans (e.g.: 1200000000 becomes "1.2
-
# Billion"). This is useful for numbers that can get very large
-
# (and too hard to read).
-
#
-
# See <tt>number_to_human_size</tt> if you want to print a file
-
# size.
-
#
-
# You can also define your own unit-quantifier names if you want
-
# to use other decimal units (e.g.: 1500 becomes "1.5
-
# kilometers", 0.150 becomes "150 milliliters", etc). You may
-
# define a wide range of unit quantifiers, even fractional ones
-
# (centi, deci, mili, etc).
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:round_mode</tt> - Determine how rounding is performed
-
# (defaults to :default. See BigDecimal::mode)
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:units</tt> - A Hash of unit quantifier names. Or a
-
# string containing an i18n scope where to find this hash. It
-
# might have the following keys:
-
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
-
# <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
-
# <tt>:billion</tt>, <tt>:trillion</tt>,
-
# <tt>:quadrillion</tt>
-
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
-
# <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
-
# <tt>:pico</tt>, <tt>:femto</tt>
-
# * <tt>:format</tt> - Sets the format of the output string
-
# (defaults to "%n %u"). The field types are:
-
# * %u - The quantifier (ex.: 'thousand')
-
# * %n - The number
-
#
-
# ==== Examples
-
#
-
# number_to_human(123) # => "123"
-
# number_to_human(1234) # => "1.23 Thousand"
-
# number_to_human(12345) # => "12.3 Thousand"
-
# number_to_human(1234567) # => "1.23 Million"
-
# number_to_human(1234567890) # => "1.23 Billion"
-
# number_to_human(1234567890123) # => "1.23 Trillion"
-
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
-
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
-
# number_to_human(489939, precision: 2) # => "490 Thousand"
-
# number_to_human(489939, precision: 4) # => "489.9 Thousand"
-
# number_to_human(489939, precision: 2
-
# , round_mode: :down) # => "480 Thousand"
-
# number_to_human(1234567, precision: 4,
-
# significant: false) # => "1.2346 Million"
-
# number_to_human(1234567, precision: 1,
-
# separator: ',',
-
# significant: false) # => "1,2 Million"
-
#
-
# number_to_human(500000000, precision: 5) # => "500 Million"
-
# number_to_human(12345012345, significant: false) # => "12.345 Billion"
-
#
-
# Non-significant zeros after the decimal separator are stripped
-
# out by default (set <tt>:strip_insignificant_zeros</tt> to
-
# +false+ to change that):
-
#
-
# number_to_human(12.00001) # => "12"
-
# number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
-
#
-
# ==== Custom Unit Quantifiers
-
#
-
# You can also use your own custom unit quantifiers:
-
# number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt"
-
#
-
# If in your I18n locale you have:
-
#
-
# distance:
-
# centi:
-
# one: "centimeter"
-
# other: "centimeters"
-
# unit:
-
# one: "meter"
-
# other: "meters"
-
# thousand:
-
# one: "kilometer"
-
# other: "kilometers"
-
# billion: "gazillion-distance"
-
#
-
# Then you could do:
-
#
-
# number_to_human(543934, units: :distance) # => "544 kilometers"
-
# number_to_human(54393498, units: :distance) # => "54400 kilometers"
-
# number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
-
# number_to_human(343, units: :distance, precision: 1) # => "300 meters"
-
# number_to_human(1, units: :distance) # => "1 meter"
-
# number_to_human(0.34, units: :distance) # => "34 centimeters"
-
1
def number_to_human(number, options = {})
-
NumberToHumanConverter.convert(number, options)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/big_decimal/conversions"
-
require "active_support/core_ext/object/blank"
-
require "active_support/core_ext/hash/keys"
-
require "active_support/i18n"
-
require "active_support/core_ext/class/attribute"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberConverter # :nodoc:
-
# Default and i18n option namespace per class
-
class_attribute :namespace
-
-
# Does the object need a number that is a valid float?
-
class_attribute :validate_float
-
-
attr_reader :number, :opts
-
-
DEFAULTS = {
-
# Used in number_to_delimited
-
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
-
format: {
-
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
-
separator: ".",
-
# Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
-
delimiter: ",",
-
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
-
precision: 3,
-
# If set to true, precision will mean the number of significant digits instead
-
# of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
-
significant: false,
-
# If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2)
-
strip_insignificant_zeros: false
-
},
-
-
# Used in number_to_currency
-
currency: {
-
format: {
-
format: "%u%n",
-
negative_format: "-%u%n",
-
unit: "$",
-
# These five are to override number.format and are optional
-
separator: ".",
-
delimiter: ",",
-
precision: 2,
-
significant: false,
-
strip_insignificant_zeros: false
-
}
-
},
-
-
# Used in number_to_percentage
-
percentage: {
-
format: {
-
delimiter: "",
-
format: "%n%"
-
}
-
},
-
-
# Used in number_to_rounded
-
precision: {
-
format: {
-
delimiter: ""
-
}
-
},
-
-
# Used in number_to_human_size and number_to_human
-
human: {
-
format: {
-
# These five are to override number.format and are optional
-
delimiter: "",
-
precision: 3,
-
significant: true,
-
strip_insignificant_zeros: true
-
},
-
# Used in number_to_human_size
-
storage_units: {
-
# Storage units output formatting.
-
# %u is the storage unit, %n is the number (default: 2 MB)
-
format: "%n %u",
-
units: {
-
byte: "Bytes",
-
kb: "KB",
-
mb: "MB",
-
gb: "GB",
-
tb: "TB"
-
}
-
},
-
# Used in number_to_human
-
decimal_units: {
-
format: "%n %u",
-
# Decimal units output formatting
-
# By default we will only quantify some of the exponents
-
# but the commented ones might be defined or overridden
-
# by the user.
-
units: {
-
# femto: Quadrillionth
-
# pico: Trillionth
-
# nano: Billionth
-
# micro: Millionth
-
# mili: Thousandth
-
# centi: Hundredth
-
# deci: Tenth
-
unit: "",
-
# ten:
-
# one: Ten
-
# other: Tens
-
# hundred: Hundred
-
thousand: "Thousand",
-
million: "Million",
-
billion: "Billion",
-
trillion: "Trillion",
-
quadrillion: "Quadrillion"
-
}
-
}
-
}
-
}
-
-
def self.convert(number, options)
-
new(number, options).execute
-
end
-
-
def initialize(number, options)
-
@number = number
-
@opts = options.symbolize_keys
-
end
-
-
def execute
-
if !number
-
nil
-
elsif validate_float? && !valid_float?
-
number
-
else
-
convert
-
end
-
end
-
-
private
-
def options
-
@options ||= format_options.merge(opts)
-
end
-
-
def format_options
-
default_format_options.merge!(i18n_format_options)
-
end
-
-
def default_format_options
-
options = DEFAULTS[:format].dup
-
options.merge!(DEFAULTS[namespace][:format]) if namespace
-
options
-
end
-
-
def i18n_format_options
-
locale = opts[:locale]
-
options = I18n.translate(:'number.format', locale: locale, default: {}).dup
-
-
if namespace
-
options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
-
end
-
-
options
-
end
-
-
def translate_number_value_with_default(key, **i18n_options)
-
I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
-
end
-
-
def translate_in_locale(key, **i18n_options)
-
translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
-
end
-
-
def default_value(key)
-
key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
-
end
-
-
def valid_float?
-
Float(number)
-
rescue ArgumentError, TypeError
-
false
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToCurrencyConverter < NumberConverter # :nodoc:
-
self.namespace = :currency
-
-
def convert
-
number = self.number.to_s.strip
-
number_f = number.to_f
-
format = options[:format]
-
-
if number_f.negative?
-
number = number_f.abs
-
-
unless options[:precision] == 0 && number < 0.5
-
format = options[:negative_format]
-
end
-
end
-
-
rounded_number = NumberToRoundedConverter.convert(number, options)
-
format.gsub("%n", rounded_number).gsub("%u", options[:unit])
-
end
-
-
private
-
def options
-
@options ||= begin
-
defaults = default_format_options.merge(i18n_opts)
-
# Override negative format if format options are given
-
defaults[:negative_format] = "-#{opts[:format]}" if opts[:format]
-
defaults.merge!(opts)
-
end
-
end
-
-
def i18n_opts
-
# Set International negative format if it does not exist
-
i18n = i18n_format_options
-
i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format]
-
i18n
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToDelimitedConverter < NumberConverter #:nodoc:
-
self.validate_float = true
-
-
DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
-
-
def convert
-
parts.join(options[:separator])
-
end
-
-
private
-
def parts
-
left, right = number.to_s.split(".")
-
left.gsub!(delimiter_pattern) do |digit_to_delimit|
-
"#{digit_to_delimit}#{options[:delimiter]}"
-
end
-
[left, right].compact
-
end
-
-
def delimiter_pattern
-
options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToHumanConverter < NumberConverter # :nodoc:
-
DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
-
INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
-
-
self.namespace = :human
-
self.validate_float = true
-
-
def convert # :nodoc:
-
@number = RoundingHelper.new(options).round(number)
-
@number = Float(number)
-
-
# For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
-
unless options.key?(:strip_insignificant_zeros)
-
options[:strip_insignificant_zeros] = true
-
end
-
-
units = opts[:units]
-
exponent = calculate_exponent(units)
-
@number = number / (10**exponent)
-
-
rounded_number = NumberToRoundedConverter.convert(number, options)
-
unit = determine_unit(units, exponent)
-
format.gsub("%n", rounded_number).gsub("%u", unit).strip
-
end
-
-
private
-
def format
-
options[:format] || translate_in_locale("human.decimal_units.format")
-
end
-
-
def determine_unit(units, exponent)
-
exp = DECIMAL_UNITS[exponent]
-
case units
-
when Hash
-
units[exp] || ""
-
when String, Symbol
-
I18n.translate("#{units}.#{exp}", locale: options[:locale], count: number.to_i)
-
else
-
translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i)
-
end
-
end
-
-
def calculate_exponent(units)
-
exponent = number != 0 ? Math.log10(number.abs).floor : 0
-
unit_exponents(units).find { |e| exponent >= e } || 0
-
end
-
-
def unit_exponents(units)
-
case units
-
when Hash
-
units
-
when String, Symbol
-
I18n.translate(units.to_s, locale: options[:locale], raise: true)
-
when nil
-
translate_in_locale("human.decimal_units.units", raise: true)
-
else
-
raise ArgumentError, ":units must be a Hash or String translation scope."
-
end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
-
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
-
-
self.namespace = :human
-
self.validate_float = true
-
-
def convert
-
@number = Float(number)
-
-
# For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
-
unless options.key?(:strip_insignificant_zeros)
-
options[:strip_insignificant_zeros] = true
-
end
-
-
if smaller_than_base?
-
number_to_format = number.to_i.to_s
-
else
-
human_size = number / (base**exponent)
-
number_to_format = NumberToRoundedConverter.convert(human_size, options)
-
end
-
conversion_format.gsub("%n", number_to_format).gsub("%u", unit)
-
end
-
-
private
-
def conversion_format
-
translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true)
-
end
-
-
def unit
-
translate_number_value_with_default(storage_unit_key, locale: options[:locale], count: number.to_i, raise: true)
-
end
-
-
def storage_unit_key
-
key_end = smaller_than_base? ? "byte" : STORAGE_UNITS[exponent]
-
"human.storage_units.units.#{key_end}"
-
end
-
-
def exponent
-
max = STORAGE_UNITS.size - 1
-
exp = (Math.log(number) / Math.log(base)).to_i
-
exp = max if exp > max # avoid overflow for the highest unit
-
exp
-
end
-
-
def smaller_than_base?
-
number.to_i < base
-
end
-
-
def base
-
1024
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToPercentageConverter < NumberConverter # :nodoc:
-
self.namespace = :percentage
-
-
def convert
-
rounded_number = NumberToRoundedConverter.convert(number, options)
-
options[:format].gsub("%n", rounded_number)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToPhoneConverter < NumberConverter #:nodoc:
-
def convert
-
str = country_code(opts[:country_code]).dup
-
str << convert_to_phone_number(number.to_s.strip)
-
str << phone_ext(opts[:extension])
-
end
-
-
private
-
def convert_to_phone_number(number)
-
if opts[:area_code]
-
convert_with_area_code(number)
-
else
-
convert_without_area_code(number)
-
end
-
end
-
-
def convert_with_area_code(number)
-
default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/
-
number.gsub!(regexp_pattern(default_pattern),
-
"(\\1) \\2#{delimiter}\\3")
-
number
-
end
-
-
def convert_without_area_code(number)
-
default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
-
number.gsub!(regexp_pattern(default_pattern),
-
"\\1#{delimiter}\\2#{delimiter}\\3")
-
number.slice!(0, 1) if start_with_delimiter?(number)
-
number
-
end
-
-
def start_with_delimiter?(number)
-
delimiter.present? && number.start_with?(delimiter)
-
end
-
-
def delimiter
-
opts[:delimiter] || "-"
-
end
-
-
def country_code(code)
-
code.blank? ? "" : "+#{code}#{delimiter}"
-
end
-
-
def phone_ext(ext)
-
ext.blank? ? "" : " x #{ext}"
-
end
-
-
def regexp_pattern(default_pattern)
-
opts.fetch :pattern, default_pattern
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/number_helper/number_converter"
-
-
module ActiveSupport
-
module NumberHelper
-
class NumberToRoundedConverter < NumberConverter # :nodoc:
-
self.namespace = :precision
-
self.validate_float = true
-
-
def convert
-
helper = RoundingHelper.new(options)
-
rounded_number = helper.round(number)
-
-
if precision = options[:precision]
-
if options[:significant] && precision > 0
-
digits = helper.digit_count(rounded_number)
-
precision -= digits
-
precision = 0 if precision < 0 # don't let it be negative
-
end
-
-
formatted_string =
-
if rounded_number.nan? || rounded_number.infinite? || rounded_number == rounded_number.to_i
-
"%00.#{precision}f" % rounded_number
-
else
-
s = rounded_number.to_s("F")
-
s << "0" * precision
-
a, b = s.split(".", 2)
-
a << "."
-
a << b[0, precision]
-
end
-
else
-
formatted_string = rounded_number
-
end
-
-
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
-
format_number(delimited_number)
-
end
-
-
private
-
def strip_insignificant_zeros
-
options[:strip_insignificant_zeros]
-
end
-
-
def format_number(number)
-
if strip_insignificant_zeros
-
escaped_separator = Regexp.escape(options[:separator])
-
number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, "")
-
else
-
number
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
module NumberHelper
-
class RoundingHelper # :nodoc:
-
attr_reader :options
-
-
def initialize(options)
-
@options = options
-
end
-
-
def round(number)
-
precision = absolute_precision(number)
-
return number unless precision
-
-
rounded_number = convert_to_decimal(number).round(precision, options.fetch(:round_mode, :default))
-
rounded_number.zero? ? rounded_number.abs : rounded_number # prevent showing negative zeros
-
end
-
-
def digit_count(number)
-
return 1 if number.zero?
-
(Math.log10(number.abs) + 1).floor
-
end
-
-
private
-
def convert_to_decimal(number)
-
case number
-
when Float, String
-
BigDecimal(number.to_s)
-
when Rational
-
BigDecimal(number, digit_count(number.to_i) + options[:precision])
-
else
-
number.to_d
-
end
-
end
-
-
def absolute_precision(number)
-
if significant && options[:precision] > 0
-
options[:precision] - digit_count(convert_to_decimal(number))
-
else
-
options[:precision]
-
end
-
end
-
-
def significant
-
options[:significant]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/hash/deep_merge"
-
2
require "active_support/core_ext/symbol/starts_ends_with"
-
-
2
module ActiveSupport
-
2
class OptionMerger #:nodoc:
-
2
instance_methods.each do |method|
-
152
undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
-
end
-
-
2
def initialize(context, options)
-
@context, @options = context, options
-
end
-
-
2
private
-
2
def method_missing(method, *arguments, &block)
-
options = nil
-
if arguments.first.is_a?(Proc)
-
proc = arguments.pop
-
arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
-
elsif arguments.last.respond_to?(:to_hash)
-
options = @options.deep_merge(arguments.pop)
-
else
-
options = @options
-
end
-
-
invoke_method(method, arguments, options, &block)
-
end
-
-
2
if RUBY_VERSION >= "2.7"
-
def invoke_method(method, arguments, options, &block)
-
if options
-
@context.__send__(method, *arguments, **options, &block)
-
else
-
@context.__send__(method, *arguments, &block)
-
end
-
end
-
else
-
2
def invoke_method(method, arguments, options, &block)
-
arguments << options.dup if options
-
@context.__send__(method, *arguments, &block)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "yaml"
-
-
1
YAML.add_builtin_type("omap") do |type, val|
-
ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }]
-
end
-
-
1
module ActiveSupport
-
# DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
-
# insertion order.
-
#
-
# oh = ActiveSupport::OrderedHash.new
-
# oh[:a] = 1
-
# oh[:b] = 2
-
# oh.keys # => [:a, :b], this order is guaranteed
-
#
-
# Also, maps the +omap+ feature for YAML files
-
# (See https://yaml.org/type/omap.html) to support ordered items
-
# when loading from yaml.
-
#
-
# <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
-
# with other implementations.
-
1
class OrderedHash < ::Hash
-
1
def to_yaml_type
-
"!tag:yaml.org,2002:omap"
-
end
-
-
1
def encode_with(coder)
-
coder.represent_seq "!omap", map { |k, v| { k => v } }
-
end
-
-
1
def select(*args, &block)
-
dup.tap { |hash| hash.select!(*args, &block) }
-
end
-
-
1
def reject(*args, &block)
-
dup.tap { |hash| hash.reject!(*args, &block) }
-
end
-
-
1
def nested_under_indifferent_access
-
self
-
end
-
-
# Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt>
-
1
def extractable_options?
-
true
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/object/blank"
-
-
2
module ActiveSupport
-
# +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods.
-
#
-
# With a +Hash+, key-value pairs are typically managed like this:
-
#
-
# h = {}
-
# h[:boy] = 'John'
-
# h[:girl] = 'Mary'
-
# h[:boy] # => 'John'
-
# h[:girl] # => 'Mary'
-
# h[:dog] # => nil
-
#
-
# Using +OrderedOptions+, the above code can be written as:
-
#
-
# h = ActiveSupport::OrderedOptions.new
-
# h.boy = 'John'
-
# h.girl = 'Mary'
-
# h.boy # => 'John'
-
# h.girl # => 'Mary'
-
# h.dog # => nil
-
#
-
# To raise an exception when the value is blank, append a
-
# bang to the key name, like:
-
#
-
# h.dog! # => raises KeyError: :dog is blank
-
#
-
2
class OrderedOptions < Hash
-
2
alias_method :_get, :[] # preserve the original #[] method
-
2
protected :_get # make it protected
-
-
2
def []=(key, value)
-
super(key.to_sym, value)
-
end
-
-
2
def [](key)
-
super(key.to_sym)
-
end
-
-
2
def method_missing(name, *args)
-
name_string = +name.to_s
-
if name_string.chomp!("=")
-
self[name_string] = args.first
-
else
-
bangs = name_string.chomp!("!")
-
-
if bangs
-
self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
-
else
-
self[name_string]
-
end
-
end
-
end
-
-
2
def respond_to_missing?(name, include_private)
-
true
-
end
-
-
2
def extractable_options?
-
true
-
end
-
end
-
-
# +InheritableOptions+ provides a constructor to build an +OrderedOptions+
-
# hash inherited from another hash.
-
#
-
# Use this if you already have some hash and you want to create a new one based on it.
-
#
-
# h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
-
# h.girl # => 'Mary'
-
# h.boy # => 'John'
-
2
class InheritableOptions < OrderedOptions
-
2
def initialize(parent = nil)
-
if parent.kind_of?(OrderedOptions)
-
# use the faster _get when dealing with OrderedOptions
-
super() { |h, k| parent._get(k) }
-
elsif parent
-
super() { |h, k| parent[k] }
-
else
-
super()
-
end
-
end
-
-
2
def inheritable_copy
-
self.class.new(self)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/object/duplicable"
-
-
1
module ActiveSupport
-
# +ParameterFilter+ allows you to specify keys for sensitive data from
-
# hash-like object and replace corresponding value. Filtering only certain
-
# sub-keys from a hash is possible by using the dot notation:
-
# 'credit_card.number'. If a proc is given, each key and value of a hash and
-
# all sub-hashes are passed to it, where the value or the key can be replaced
-
# using String#replace or similar methods.
-
#
-
# ActiveSupport::ParameterFilter.new([:password])
-
# => replaces the value to all keys matching /password/i with "[FILTERED]"
-
#
-
# ActiveSupport::ParameterFilter.new([:foo, "bar"])
-
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
-
#
-
# ActiveSupport::ParameterFilter.new(["credit_card.code"])
-
# => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
-
# change { file: { code: "xxxx"} }
-
#
-
# ActiveSupport::ParameterFilter.new([-> (k, v) do
-
# v.reverse! if /secret/i.match?(k)
-
# end])
-
# => reverses the value to all keys matching /secret/i
-
1
class ParameterFilter
-
1
FILTERED = "[FILTERED]" # :nodoc:
-
-
# Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
-
# Other types of filters are treated as +String+ using +to_s+.
-
# For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
-
#
-
# ==== Options
-
#
-
# * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
-
1
def initialize(filters = [], mask: FILTERED)
-
@filters = filters
-
@mask = mask
-
end
-
-
# Mask value of +params+ if key matches one of filters.
-
1
def filter(params)
-
compiled_filter.call(params)
-
end
-
-
# Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
-
1
def filter_param(key, value)
-
@filters.empty? ? value : compiled_filter.value_for_key(key, value)
-
end
-
-
1
private
-
1
def compiled_filter
-
@compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
-
end
-
-
1
class CompiledFilter # :nodoc:
-
1
def self.compile(filters, mask:)
-
return lambda { |params| params.dup } if filters.empty?
-
-
strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, nil
-
-
filters.each do |item|
-
case item
-
when Proc
-
blocks << item
-
when Regexp
-
if item.to_s.include?("\\.")
-
(deep_regexps ||= []) << item
-
else
-
regexps << item
-
end
-
else
-
s = Regexp.escape(item.to_s)
-
if s.include?("\\.")
-
(deep_strings ||= []) << s
-
else
-
strings << s
-
end
-
end
-
end
-
-
regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
-
(deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings&.any?
-
-
new regexps, deep_regexps, blocks, mask: mask
-
end
-
-
1
attr_reader :regexps, :deep_regexps, :blocks
-
-
1
def initialize(regexps, deep_regexps, blocks, mask:)
-
@regexps = regexps
-
@deep_regexps = deep_regexps&.any? ? deep_regexps : nil
-
@blocks = blocks
-
@mask = mask
-
end
-
-
1
def call(params, parents = [], original_params = params)
-
filtered_params = params.class.new
-
-
params.each do |key, value|
-
filtered_params[key] = value_for_key(key, value, parents, original_params)
-
end
-
-
filtered_params
-
end
-
-
1
def value_for_key(key, value, parents = [], original_params = nil)
-
parents.push(key) if deep_regexps
-
if regexps.any? { |r| r.match?(key.to_s) }
-
value = @mask
-
elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
-
value = @mask
-
elsif value.is_a?(Hash)
-
value = call(value, parents, original_params)
-
elsif value.is_a?(Array)
-
# If we don't pop the current parent it will be duplicated as we
-
# process each array value.
-
parents.pop if deep_regexps
-
value = value.map { |v| value_for_key(key, v, parents, original_params) }
-
# Restore the parent stack after processing the array.
-
parents.push(key) if deep_regexps
-
elsif blocks.any?
-
key = key.dup if key.duplicable?
-
value = value.dup if value.duplicable?
-
blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
-
end
-
parents.pop if deep_regexps
-
value
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "active_support/core_ext/module/delegation"
-
-
24
module ActiveSupport
-
# NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends.
-
# Please use that approach instead.
-
#
-
# This module is used to encapsulate access to thread local variables.
-
#
-
# Instead of polluting the thread locals namespace:
-
#
-
# Thread.current[:connection_handler]
-
#
-
# you define a class that extends this module:
-
#
-
# module ActiveRecord
-
# class RuntimeRegistry
-
# extend ActiveSupport::PerThreadRegistry
-
#
-
# attr_accessor :connection_handler
-
# end
-
# end
-
#
-
# and invoke the declared instance accessors as class methods. So
-
#
-
# ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
-
#
-
# sets a connection handler local to the current thread, and
-
#
-
# ActiveRecord::RuntimeRegistry.connection_handler
-
#
-
# returns a connection handler local to the current thread.
-
#
-
# This feature is accomplished by instantiating the class and storing the
-
# instance as a thread local keyed by the class name. In the example above
-
# a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
-
# The class methods proxy to said thread local instance.
-
#
-
# If the class has an initializer, it must accept no arguments.
-
24
module PerThreadRegistry
-
24
def self.extended(object)
-
29
object.instance_variable_set :@per_thread_registry_key, object.name.freeze
-
end
-
-
24
def instance
-
Thread.current[@per_thread_registry_key] ||= new
-
end
-
-
24
private
-
24
def method_missing(name, *args, &block)
-
# Caches the method definition as a singleton method of the receiver.
-
#
-
# By letting #delegate handle it, we avoid an enclosure that'll capture args.
-
singleton_class.delegate name, to: :instance
-
-
send(name, *args, &block)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveSupport
-
# A class with no predefined methods that behaves similarly to Builder's
-
# BlankSlate. Used for proxy classes.
-
class ProxyObject < ::BasicObject
-
undef_method :==
-
undef_method :equal?
-
-
# Let ActiveSupport::ProxyObject at least raise exceptions.
-
def raise(*args)
-
::Object.send(:raise, *args)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# This is a private interface.
-
#
-
# Rails components cherry pick from Active Support as needed, but there are a
-
# few features that are used for sure in some way or another and it is not worth
-
# putting individual requires absolutely everywhere. Think blank? for example.
-
#
-
# This file is loaded by every Rails component except Active Support itself,
-
# but it does not belong to the Rails public interface. It is internal to
-
# Rails and can change anytime.
-
-
# Defines Object#blank? and Object#present?.
-
require "active_support/core_ext/object/blank"
-
-
# Support for ClassMethods and the included macro.
-
require "active_support/concern"
-
-
# Defines Class#class_attribute.
-
require "active_support/core_ext/class/attribute"
-
-
# Defines Module#delegate.
-
require "active_support/core_ext/module/delegation"
-
-
# Defines ActiveSupport::Deprecation.
-
require "active_support/deprecation"
-
# frozen_string_literal: true
-
-
require "active_support"
-
require "active_support/i18n_railtie"
-
-
module ActiveSupport
-
class Railtie < Rails::Railtie # :nodoc:
-
config.active_support = ActiveSupport::OrderedOptions.new
-
-
config.eager_load_namespaces << ActiveSupport
-
-
initializer "active_support.set_authenticated_message_encryption" do |app|
-
config.after_initialize do
-
unless app.config.active_support.use_authenticated_message_encryption.nil?
-
ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
-
app.config.active_support.use_authenticated_message_encryption
-
end
-
end
-
end
-
-
initializer "active_support.reset_all_current_attributes_instances" do |app|
-
app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
-
app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
-
app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
-
-
ActiveSupport.on_load(:active_support_test_case) do
-
require "active_support/current_attributes/test_helper"
-
include ActiveSupport::CurrentAttributes::TestHelper
-
end
-
end
-
-
initializer "active_support.deprecation_behavior" do |app|
-
if deprecation = app.config.active_support.deprecation
-
ActiveSupport::Deprecation.behavior = deprecation
-
end
-
-
if disallowed_deprecation = app.config.active_support.disallowed_deprecation
-
ActiveSupport::Deprecation.disallowed_behavior = disallowed_deprecation
-
end
-
-
if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings
-
ActiveSupport::Deprecation.disallowed_warnings = disallowed_warnings
-
end
-
end
-
-
# Sets the default value for Time.zone
-
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
-
initializer "active_support.initialize_time_zone" do |app|
-
begin
-
TZInfo::DataSource.get
-
rescue TZInfo::DataSourceNotFound => e
-
raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
-
end
-
require "active_support/core_ext/time/zones"
-
Time.zone_default = Time.find_zone!(app.config.time_zone)
-
end
-
-
# Sets the default week start
-
# If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
-
initializer "active_support.initialize_beginning_of_week" do |app|
-
require "active_support/core_ext/date/calculations"
-
beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week)
-
-
Date.beginning_of_week_default = beginning_of_week_default
-
end
-
-
initializer "active_support.require_master_key" do |app|
-
if app.config.respond_to?(:require_master_key) && app.config.require_master_key
-
begin
-
app.credentials.key
-
rescue ActiveSupport::EncryptedFile::MissingKeyError => error
-
$stderr.puts error.message
-
exit 1
-
end
-
end
-
end
-
-
initializer "active_support.set_configs" do |app|
-
app.config.active_support.each do |k, v|
-
k = "#{k}="
-
ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
-
end
-
end
-
-
initializer "active_support.set_hash_digest_class" do |app|
-
config.after_initialize do
-
if app.config.active_support.use_sha1_digests
-
ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/execution_wrapper"
-
require "active_support/executor"
-
-
module ActiveSupport
-
#--
-
# This class defines several callbacks:
-
#
-
# to_prepare -- Run once at application startup, and also from
-
# +to_run+.
-
#
-
# to_run -- Run before a work run that is reloading. If
-
# +reload_classes_only_on_change+ is true (the default), the class
-
# unload will have already occurred.
-
#
-
# to_complete -- Run after a work run that has reloaded. If
-
# +reload_classes_only_on_change+ is false, the class unload will
-
# have occurred after the work run, but before this callback.
-
#
-
# before_class_unload -- Run immediately before the classes are
-
# unloaded.
-
#
-
# after_class_unload -- Run immediately after the classes are
-
# unloaded.
-
#
-
class Reloader < ExecutionWrapper
-
define_callbacks :prepare
-
-
define_callbacks :class_unload
-
-
# Registers a callback that will run once at application startup and every time the code is reloaded.
-
def self.to_prepare(*args, &block)
-
set_callback(:prepare, *args, &block)
-
end
-
-
# Registers a callback that will run immediately before the classes are unloaded.
-
def self.before_class_unload(*args, &block)
-
set_callback(:class_unload, *args, &block)
-
end
-
-
# Registers a callback that will run immediately after the classes are unloaded.
-
def self.after_class_unload(*args, &block)
-
set_callback(:class_unload, :after, *args, &block)
-
end
-
-
to_run(:after) { self.class.prepare! }
-
-
# Initiate a manual reload
-
def self.reload!
-
executor.wrap do
-
new.tap do |instance|
-
instance.run!
-
ensure
-
instance.complete!
-
end
-
end
-
prepare!
-
end
-
-
def self.run! # :nodoc:
-
if check!
-
super
-
else
-
Null
-
end
-
end
-
-
# Run the supplied block as a work unit, reloading code as needed
-
def self.wrap
-
executor.wrap do
-
super
-
end
-
end
-
-
class_attribute :executor, default: Executor
-
class_attribute :check, default: lambda { false }
-
-
def self.check! # :nodoc:
-
@should_reload ||= check.call
-
end
-
-
def self.reloaded! # :nodoc:
-
@should_reload = false
-
end
-
-
def self.prepare! # :nodoc:
-
new.run_callbacks(:prepare)
-
end
-
-
def initialize
-
super
-
@locked = false
-
end
-
-
# Acquire the ActiveSupport::Dependencies::Interlock unload lock,
-
# ensuring it will be released automatically
-
def require_unload_lock!
-
unless @locked
-
ActiveSupport::Dependencies.interlock.start_unloading
-
@locked = true
-
end
-
end
-
-
# Release the unload lock if it has been previously obtained
-
def release_unload_lock!
-
if @locked
-
@locked = false
-
ActiveSupport::Dependencies.interlock.done_unloading
-
end
-
end
-
-
def run! # :nodoc:
-
super
-
release_unload_lock!
-
end
-
-
def class_unload!(&block) # :nodoc:
-
require_unload_lock!
-
run_callbacks(:class_unload, &block)
-
end
-
-
def complete! # :nodoc:
-
super
-
self.class.reloaded!
-
ensure
-
release_unload_lock!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/concern"
-
1
require "active_support/core_ext/class/attribute"
-
1
require "active_support/core_ext/string/inflections"
-
-
1
module ActiveSupport
-
# Rescuable module adds support for easier exception handling.
-
1
module Rescuable
-
1
extend Concern
-
-
1
included do
-
1
class_attribute :rescue_handlers, default: []
-
end
-
-
1
module ClassMethods
-
# Rescue exceptions raised in controller actions.
-
#
-
# <tt>rescue_from</tt> receives a series of exception classes or class
-
# names, and a trailing <tt>:with</tt> option with the name of a method
-
# or a Proc object to be called to handle them. Alternatively a block can
-
# be given.
-
#
-
# Handlers that take one argument will be called with the exception, so
-
# that the exception can be inspected when dealing with it.
-
#
-
# Handlers are inherited. They are searched from right to left, from
-
# bottom to top, and up the hierarchy. The handler of the first class for
-
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
-
# any.
-
#
-
# class ApplicationController < ActionController::Base
-
# rescue_from User::NotAuthorized, with: :deny_access # self defined exception
-
# rescue_from ActiveRecord::RecordInvalid, with: :show_errors
-
#
-
# rescue_from 'MyAppError::Base' do |exception|
-
# render xml: exception, status: 500
-
# end
-
#
-
# private
-
# def deny_access
-
# ...
-
# end
-
#
-
# def show_errors(exception)
-
# exception.record.new_record? ? ...
-
# end
-
# end
-
#
-
# Exceptions raised inside exception handlers are not propagated up.
-
1
def rescue_from(*klasses, with: nil, &block)
-
6
unless with
-
3
if block_given?
-
3
with = block
-
else
-
raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block."
-
end
-
end
-
-
6
klasses.each do |klass|
-
6
key = if klass.is_a?(Module) && klass.respond_to?(:===)
-
5
klass.name
-
1
elsif klass.is_a?(String)
-
1
klass
-
else
-
raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
-
end
-
-
# Put the new handler at the end because the list is read in reverse.
-
6
self.rescue_handlers += [[key, with]]
-
end
-
end
-
-
# Matches an exception to a handler based on the exception class.
-
#
-
# If no handler matches the exception, check for a handler matching the
-
# (optional) exception.cause. If no handler matches the exception or its
-
# cause, this returns +nil+, so you can deal with unhandled exceptions.
-
# Be sure to re-raise unhandled exceptions if this is what you expect.
-
#
-
# begin
-
# …
-
# rescue => exception
-
# rescue_with_handler(exception) || raise
-
# end
-
#
-
# Returns the exception if it was handled and +nil+ if it was not.
-
1
def rescue_with_handler(exception, object: self, visited_exceptions: [])
-
visited_exceptions << exception
-
-
if handler = handler_for_rescue(exception, object: object)
-
handler.call exception
-
exception
-
elsif exception
-
if visited_exceptions.include?(exception.cause)
-
nil
-
else
-
rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
-
end
-
end
-
end
-
-
1
def handler_for_rescue(exception, object: self) #:nodoc:
-
case rescuer = find_rescue_handler(exception)
-
when Symbol
-
method = object.method(rescuer)
-
if method.arity == 0
-
-> e { method.call }
-
else
-
method
-
end
-
when Proc
-
if rescuer.arity == 0
-
-> e { object.instance_exec(&rescuer) }
-
else
-
-> e { object.instance_exec(e, &rescuer) }
-
end
-
end
-
end
-
-
1
private
-
1
def find_rescue_handler(exception)
-
if exception
-
# Handlers are in order of declaration but the most recently declared
-
# is the highest priority match, so we search for matching handlers
-
# in reverse.
-
_, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
-
if klass = constantize_rescue_handler_class(class_or_name)
-
klass === exception
-
end
-
end
-
-
handler
-
end
-
end
-
-
1
def constantize_rescue_handler_class(class_or_name)
-
case class_or_name
-
when String, Symbol
-
begin
-
# Try a lexical lookup first since we support
-
#
-
# class Super
-
# rescue_from 'Error', with: …
-
# end
-
#
-
# class Sub
-
# class Error < StandardError; end
-
# end
-
#
-
# so an Error raised in Sub will hit the 'Error' handler.
-
const_get class_or_name
-
rescue NameError
-
class_or_name.safe_constantize
-
end
-
else
-
class_or_name
-
end
-
end
-
end
-
-
# Delegates to the class method, but uses the instance as the subject for
-
# rescue_from handlers (method calls, instance_exec blocks).
-
1
def rescue_with_handler(exception)
-
self.class.rescue_with_handler exception, object: self
-
end
-
-
# Internal handler lookup. Delegates to class method. Some libraries call
-
# this directly, so keeping it around for compatibility.
-
1
def handler_for_rescue(exception) #:nodoc:
-
self.class.handler_for_rescue exception, object: self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/security_utils"
-
1
require "active_support/messages/rotator"
-
-
1
module ActiveSupport
-
# The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
-
# and allows you to rotate a previously defined value to a new one.
-
#
-
# It can be used as follow:
-
#
-
# rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
-
# rotator.rotate('previous_production_value')
-
# rotator.secure_compare!('previous_production_value')
-
#
-
# One real use case example would be to rotate a basic auth credentials:
-
#
-
# class MyController < ApplicationController
-
# def authenticate_request
-
# rotator = ActiveSupport::SecureComparerotator.new('new_password')
-
# rotator.rotate('old_password')
-
#
-
# authenticate_or_request_with_http_basic do |username, password|
-
# rotator.secure_compare!(password)
-
# rescue ActiveSupport::SecureCompareRotator::InvalidMatch
-
# false
-
# end
-
# end
-
# end
-
1
class SecureCompareRotator
-
1
include SecurityUtils
-
1
prepend Messages::Rotator
-
-
1
InvalidMatch = Class.new(StandardError)
-
-
1
def initialize(value, **_options)
-
@value = value
-
end
-
-
1
def secure_compare!(other_value, on_rotation: @on_rotation)
-
secure_compare(@value, other_value) ||
-
run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
-
raise(InvalidMatch)
-
end
-
-
1
private
-
1
def build_rotation(previous_value, _options)
-
self.class.new(previous_value)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveSupport
-
2
module SecurityUtils
-
# Constant time string comparison, for fixed length strings.
-
#
-
# The values compared should be of fixed length, such as strings
-
# that have already been processed by HMAC. Raises in case of length mismatch.
-
2
def fixed_length_secure_compare(a, b)
-
raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
-
-
l = a.unpack "C#{a.bytesize}"
-
-
res = 0
-
b.each_byte { |byte| res |= byte ^ l.shift }
-
res == 0
-
end
-
2
module_function :fixed_length_secure_compare
-
-
# Secure string comparison for strings of variable length.
-
#
-
# While a timing attack would not be able to discern the content of
-
# a secret compared via secure_compare, it is possible to determine
-
# the secret length. This should be considered when using secure_compare
-
# to compare weak, short secrets to user input.
-
2
def secure_compare(a, b)
-
a.length == b.length && fixed_length_secure_compare(a, b)
-
end
-
2
module_function :secure_compare
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/symbol/starts_ends_with"
-
-
1
module ActiveSupport
-
# Wrapping a string in this class gives you a prettier way to test
-
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
-
# in a StringInquirer object, so instead of calling this:
-
#
-
# Rails.env == 'production'
-
#
-
# you can call this:
-
#
-
# Rails.env.production?
-
#
-
# == Instantiating a new StringInquirer
-
#
-
# vehicle = ActiveSupport::StringInquirer.new('car')
-
# vehicle.car? # => true
-
# vehicle.bike? # => false
-
1
class StringInquirer < String
-
1
private
-
1
def respond_to_missing?(method_name, include_private = false)
-
method_name.end_with?("?") || super
-
end
-
-
1
def method_missing(method_name, *arguments)
-
if method_name.end_with?("?")
-
self == method_name[0..-2]
-
else
-
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/per_thread_registry"
-
1
require "active_support/notifications"
-
-
1
module ActiveSupport
-
# ActiveSupport::Subscriber is an object set to consume
-
# ActiveSupport::Notifications. The subscriber dispatches notifications to
-
# a registered object based on its given namespace.
-
#
-
# An example would be an Active Record subscriber responsible for collecting
-
# statistics about queries:
-
#
-
# module ActiveRecord
-
# class StatsSubscriber < ActiveSupport::Subscriber
-
# attach_to :active_record
-
#
-
# def sql(event)
-
# Statsd.timing("sql.#{event.payload[:name]}", event.duration)
-
# end
-
# end
-
# end
-
#
-
# After configured, whenever a "sql.active_record" notification is published,
-
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
-
# the +sql+ method.
-
#
-
# We can detach a subscriber as well:
-
#
-
# ActiveRecord::StatsSubscriber.detach_from(:active_record)
-
1
class Subscriber
-
1
class << self
-
# Attach the subscriber to a namespace.
-
1
def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications)
-
2
@namespace = namespace
-
2
@subscriber = subscriber
-
2
@notifier = notifier
-
-
2
subscribers << subscriber
-
-
# Add event subscribers for all existing methods on the class.
-
2
subscriber.public_methods(false).each do |event|
-
add_event_subscriber(event)
-
end
-
end
-
-
# Detach the subscriber from a namespace.
-
1
def detach_from(namespace, notifier = ActiveSupport::Notifications)
-
1
@namespace = namespace
-
1
@subscriber = find_attached_subscriber
-
1
@notifier = notifier
-
-
1
return unless subscriber
-
-
1
subscribers.delete(subscriber)
-
-
# Remove event subscribers of all existing methods on the class.
-
1
subscriber.public_methods(false).each do |event|
-
remove_event_subscriber(event)
-
end
-
-
# Reset notifier so that event subscribers will not add for new methods added to the class.
-
1
@notifier = nil
-
end
-
-
# Adds event subscribers for all new methods added to the class.
-
1
def method_added(event)
-
# Only public methods are added as subscribers, and only if a notifier
-
# has been set up. This means that subscribers will only be set up for
-
# classes that call #attach_to.
-
28
if public_method_defined?(event) && notifier
-
3
add_event_subscriber(event)
-
end
-
end
-
-
1
def subscribers
-
4
@@subscribers ||= []
-
end
-
-
1
private
-
1
attr_reader :subscriber, :notifier, :namespace
-
-
1
def add_event_subscriber(event) # :doc:
-
3
return if invalid_event?(event)
-
-
3
pattern = prepare_pattern(event)
-
-
# Don't add multiple subscribers (e.g. if methods are redefined).
-
3
return if pattern_subscribed?(pattern)
-
-
2
subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
-
end
-
-
1
def remove_event_subscriber(event) # :doc:
-
return if invalid_event?(event)
-
-
pattern = prepare_pattern(event)
-
-
return unless pattern_subscribed?(pattern)
-
-
notifier.unsubscribe(subscriber.patterns[pattern])
-
subscriber.patterns.delete(pattern)
-
end
-
-
1
def find_attached_subscriber
-
3
subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) }
-
end
-
-
1
def invalid_event?(event)
-
3
%i{ start finish }.include?(event.to_sym)
-
end
-
-
1
def prepare_pattern(event)
-
3
"#{event}.#{namespace}"
-
end
-
-
1
def pattern_subscribed?(pattern)
-
3
subscriber.patterns.key?(pattern)
-
end
-
end
-
-
1
attr_reader :patterns # :nodoc:
-
-
1
def initialize
-
2
@queue_key = [self.class.name, object_id].join "-"
-
2
@patterns = {}
-
2
super
-
end
-
-
1
def start(name, id, payload)
-
event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
-
event.start!
-
parent = event_stack.last
-
parent << event if parent
-
-
event_stack.push event
-
end
-
-
1
def finish(name, id, payload)
-
event = event_stack.pop
-
event.finish!
-
event.payload.merge!(payload)
-
-
method = name.split(".").first
-
send(method, event)
-
end
-
-
1
private
-
1
def event_stack
-
SubscriberQueueRegistry.instance.get_queue(@queue_key)
-
end
-
end
-
-
# This is a registry for all the event stacks kept for subscribers.
-
#
-
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
-
# for further details.
-
1
class SubscriberQueueRegistry # :nodoc:
-
1
extend PerThreadRegistry
-
-
1
def initialize
-
@registry = {}
-
end
-
-
1
def get_queue(queue_key)
-
@registry[queue_key] ||= []
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "active_support/core_ext/module/delegation"
-
1
require "active_support/core_ext/object/blank"
-
1
require "logger"
-
1
require "active_support/logger"
-
-
1
module ActiveSupport
-
# Wraps any standard Logger object to provide tagging capabilities.
-
#
-
# May be called with a block:
-
#
-
# logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
-
# logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
-
# logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
-
# logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
-
#
-
# If called without a block, a new logger will be returned with applied tags:
-
#
-
# logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
-
# logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff"
-
# logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
-
# logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
-
#
-
# This is used by the default Rails.logger as configured by Railties to make
-
# it easy to stamp log lines with subdomains, request ids, and anything else
-
# to aid debugging of multi-user production applications.
-
1
module TaggedLogging
-
1
module Formatter # :nodoc:
-
# This method is invoked when a log event occurs.
-
1
def call(severity, timestamp, progname, msg)
-
super(severity, timestamp, progname, "#{tags_text}#{msg}")
-
end
-
-
1
def tagged(*tags)
-
new_tags = push_tags(*tags)
-
yield self
-
ensure
-
pop_tags(new_tags.size)
-
end
-
-
1
def push_tags(*tags)
-
tags.flatten!
-
tags.reject!(&:blank?)
-
current_tags.concat tags
-
tags
-
end
-
-
1
def pop_tags(size = 1)
-
current_tags.pop size
-
end
-
-
1
def clear_tags!
-
current_tags.clear
-
end
-
-
1
def current_tags
-
# We use our object ID here to avoid conflicting with other instances
-
thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
-
Thread.current[thread_key] ||= []
-
end
-
-
1
def tags_text
-
tags = current_tags
-
if tags.one?
-
"[#{tags[0]}] "
-
elsif tags.any?
-
tags.collect { |tag| "[#{tag}] " }.join
-
end
-
end
-
end
-
-
1
module LocalTagStorage # :nodoc:
-
1
attr_accessor :current_tags
-
-
1
def self.extended(base)
-
base.current_tags = []
-
end
-
end
-
-
1
def self.new(logger)
-
logger = logger.dup
-
-
if logger.formatter
-
logger.formatter = logger.formatter.dup
-
else
-
# Ensure we set a default formatter so we aren't extending nil!
-
logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
-
end
-
-
logger.formatter.extend Formatter
-
logger.extend(self)
-
end
-
-
1
delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
-
-
1
def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
logger.push_tags(*formatter.current_tags, *tags)
-
logger
-
end
-
end
-
-
1
def flush
-
clear_tags!
-
super if defined?(super)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
gem "minitest" # make sure we get the gem, not stdlib
-
23
require "minitest"
-
23
require "active_support/testing/tagged_logging"
-
23
require "active_support/testing/setup_and_teardown"
-
23
require "active_support/testing/assertions"
-
23
require "active_support/testing/deprecation"
-
23
require "active_support/testing/declarative"
-
23
require "active_support/testing/isolation"
-
23
require "active_support/testing/constant_lookup"
-
23
require "active_support/testing/time_helpers"
-
23
require "active_support/testing/file_fixtures"
-
23
require "active_support/testing/parallelization"
-
23
require "concurrent/utility/processor_counter"
-
-
23
module ActiveSupport
-
23
class TestCase < ::Minitest::Test
-
23
Assertion = Minitest::Assertion
-
-
23
class << self
-
# Sets the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order = :random # => :random
-
#
-
# Valid values are:
-
# * +:random+ (to run tests in random order)
-
# * +:parallel+ (to run tests in parallel)
-
# * +:sorted+ (to run tests alphabetically by method name)
-
# * +:alpha+ (equivalent to +:sorted+)
-
23
def test_order=(new_order)
-
ActiveSupport.test_order = new_order
-
end
-
-
# Returns the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order # => :random
-
#
-
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
-
# Defaults to +:random+.
-
23
def test_order
-
898
ActiveSupport.test_order ||= :random
-
end
-
-
# Parallelizes the test suite.
-
#
-
# Takes a +workers+ argument that controls how many times the process
-
# is forked. For each process a new database will be created suffixed
-
# with the worker number.
-
#
-
# test-database-0
-
# test-database-1
-
#
-
# If <tt>ENV["PARALLEL_WORKERS"]</tt> is set the workers argument will be ignored
-
# and the environment variable will be used instead. This is useful for CI
-
# environments, or other environments where you may need more workers than
-
# you do for local testing.
-
#
-
# If the number of workers is set to +1+ or fewer, the tests will not be
-
# parallelized.
-
#
-
# If +workers+ is set to +:number_of_processors+, the number of workers will be
-
# set to the actual core count on the machine you are on.
-
#
-
# The default parallelization method is to fork processes. If you'd like to
-
# use threads instead you can pass <tt>with: :threads</tt> to the +parallelize+
-
# method. Note the threaded parallelization does not create multiple
-
# database and will not work with system tests at this time.
-
#
-
# parallelize(workers: :number_of_processors, with: :threads)
-
#
-
# The threaded parallelization uses minitest's parallel executor directly.
-
# The processes parallelization uses a Ruby DRb server.
-
23
def parallelize(workers: :number_of_processors, with: :processes)
-
23
workers = Concurrent.physical_processor_count if workers == :number_of_processors
-
23
workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
-
-
23
return if workers <= 1
-
-
23
executor = case with
-
when :processes
-
23
Testing::Parallelization.new(workers)
-
when :threads
-
Minitest::Parallel::Executor.new(workers)
-
else
-
raise ArgumentError, "#{with} is not a supported parallelization executor."
-
end
-
-
23
self.lock_threads = false if defined?(self.lock_threads) && with == :threads
-
-
23
Minitest.parallel_executor = executor
-
-
23
parallelize_me!
-
end
-
-
# Set up hook for parallel testing. This can be used if you have multiple
-
# databases or any behavior that needs to be run after the process is forked
-
# but before the tests run.
-
#
-
# Note: this feature is not available with the threaded parallelization.
-
#
-
# In your +test_helper.rb+ add the following:
-
#
-
# class ActiveSupport::TestCase
-
# parallelize_setup do
-
# # create databases
-
# end
-
# end
-
23
def parallelize_setup(&block)
-
ActiveSupport::Testing::Parallelization.after_fork_hook do |worker|
-
yield worker
-
end
-
end
-
-
# Clean up hook for parallel testing. This can be used to drop databases
-
# if your app uses multiple write/read databases or other clean up before
-
# the tests finish. This runs before the forked process is closed.
-
#
-
# Note: this feature is not available with the threaded parallelization.
-
#
-
# In your +test_helper.rb+ add the following:
-
#
-
# class ActiveSupport::TestCase
-
# parallelize_teardown do
-
# # drop databases
-
# end
-
# end
-
23
def parallelize_teardown(&block)
-
ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker|
-
yield worker
-
end
-
end
-
end
-
-
23
alias_method :method_name, :name
-
-
23
include ActiveSupport::Testing::TaggedLogging
-
23
prepend ActiveSupport::Testing::SetupAndTeardown
-
23
include ActiveSupport::Testing::Assertions
-
23
include ActiveSupport::Testing::Deprecation
-
23
include ActiveSupport::Testing::TimeHelpers
-
23
include ActiveSupport::Testing::FileFixtures
-
23
extend ActiveSupport::Testing::Declarative
-
-
# test/unit backwards compatibility methods
-
23
alias :assert_raise :assert_raises
-
23
alias :assert_not_empty :refute_empty
-
23
alias :assert_not_equal :refute_equal
-
23
alias :assert_not_in_delta :refute_in_delta
-
23
alias :assert_not_in_epsilon :refute_in_epsilon
-
23
alias :assert_not_includes :refute_includes
-
23
alias :assert_not_instance_of :refute_instance_of
-
23
alias :assert_not_kind_of :refute_kind_of
-
23
alias :assert_no_match :refute_match
-
23
alias :assert_not_nil :refute_nil
-
23
alias :assert_not_operator :refute_operator
-
23
alias :assert_not_predicate :refute_predicate
-
23
alias :assert_not_respond_to :refute_respond_to
-
23
alias :assert_not_same :refute_same
-
-
23
ActiveSupport.run_load_hooks(:active_support_test_case, self)
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/enumerable"
-
-
23
module ActiveSupport
-
23
module Testing
-
23
module Assertions
-
23
UNTRACKED = Object.new # :nodoc:
-
-
# Asserts that an expression is not truthy. Passes if <tt>object</tt> is
-
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
-
# like <tt>if foo</tt>.
-
#
-
# assert_not nil # => true
-
# assert_not false # => true
-
# assert_not 'foo' # => Expected "foo" to be nil or false
-
#
-
# An error message can be specified.
-
#
-
# assert_not foo, 'foo should be false'
-
23
def assert_not(object, message = nil)
-
message ||= "Expected #{mu_pp(object)} to be nil or false"
-
assert !object, message
-
end
-
-
# Assertion that the block should not raise an exception.
-
#
-
# Passes if evaluated code in the yielded block raises no exception.
-
#
-
# assert_nothing_raised do
-
# perform_service(param: 'no_exception')
-
# end
-
23
def assert_nothing_raised
-
yield
-
rescue => error
-
raise Minitest::UnexpectedError.new(error)
-
end
-
-
# Test numeric difference between the return value of an expression as a
-
# result of what is evaluated in the yielded block.
-
#
-
# assert_difference 'Article.count' do
-
# post :create, params: { article: {...} }
-
# end
-
#
-
# An arbitrary expression is passed in and evaluated.
-
#
-
# assert_difference 'Article.last.comments(:reload).size' do
-
# post :create, params: { comment: {...} }
-
# end
-
#
-
# An arbitrary positive or negative difference can be specified.
-
# The default is <tt>1</tt>.
-
#
-
# assert_difference 'Article.count', -1 do
-
# post :delete, params: { id: ... }
-
# end
-
#
-
# An array of expressions can also be passed in and evaluated.
-
#
-
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
-
# post :create, params: { article: {...} }
-
# end
-
#
-
# A hash of expressions/numeric differences can also be passed in and evaluated.
-
#
-
# assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
-
# post :create, params: { article: {...} }
-
# end
-
#
-
# A lambda or a list of lambdas can be passed in and evaluated:
-
#
-
# assert_difference ->{ Article.count }, 2 do
-
# post :create, params: { article: {...} }
-
# end
-
#
-
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
-
# post :create, params: { article: {...} }
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
-
# post :delete, params: { id: ... }
-
# end
-
23
def assert_difference(expression, *args, &block)
-
expressions =
-
if expression.is_a?(Hash)
-
message = args[0]
-
expression
-
else
-
difference = args[0] || 1
-
message = args[1]
-
Array(expression).index_with(difference)
-
end
-
-
exps = expressions.keys.map { |e|
-
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
-
}
-
before = exps.map(&:call)
-
-
retval = assert_nothing_raised(&block)
-
-
expressions.zip(exps, before) do |(code, diff), exp, before_value|
-
error = "#{code.inspect} didn't change by #{diff}"
-
error = "#{message}.\n#{error}" if message
-
assert_equal(before_value + diff, exp.call, error)
-
end
-
-
retval
-
end
-
-
# Assertion that the numeric result of evaluating an expression is not
-
# changed before and after invoking the passed in block.
-
#
-
# assert_no_difference 'Article.count' do
-
# post :create, params: { article: invalid_attributes }
-
# end
-
#
-
# A lambda can be passed in and evaluated.
-
#
-
# assert_no_difference -> { Article.count } do
-
# post :create, params: { article: invalid_attributes }
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_no_difference 'Article.count', 'An Article should not be created' do
-
# post :create, params: { article: invalid_attributes }
-
# end
-
#
-
# An array of expressions can also be passed in and evaluated.
-
#
-
# assert_no_difference [ 'Article.count', -> { Post.count } ] do
-
# post :create, params: { article: invalid_attributes }
-
# end
-
23
def assert_no_difference(expression, message = nil, &block)
-
assert_difference expression, 0, message, &block
-
end
-
-
# Assertion that the result of evaluating an expression is changed before
-
# and after invoking the passed in block.
-
#
-
# assert_changes 'Status.all_good?' do
-
# post :create, params: { status: { ok: false } }
-
# end
-
#
-
# You can pass the block as a string to be evaluated in the context of
-
# the block. A lambda can be passed for the block as well.
-
#
-
# assert_changes -> { Status.all_good? } do
-
# post :create, params: { status: { ok: false } }
-
# end
-
#
-
# The assertion is useful to test side effects. The passed block can be
-
# anything that can be converted to string with #to_s.
-
#
-
# assert_changes :@object do
-
# @object = 42
-
# end
-
#
-
# The keyword arguments :from and :to can be given to specify the
-
# expected initial value and the expected value after the block was
-
# executed.
-
#
-
# assert_changes :@object, from: nil, to: :foo do
-
# @object = :foo
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do
-
# post :create, params: { status: { incident: true } }
-
# end
-
23
def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block)
-
exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
-
-
before = exp.call
-
retval = assert_nothing_raised(&block)
-
-
unless from == UNTRACKED
-
error = "Expected change from #{from.inspect}"
-
error = "#{message}.\n#{error}" if message
-
assert from === before, error
-
end
-
-
after = exp.call
-
-
error = "#{expression.inspect} didn't change"
-
error = "#{error}. It was already #{to}" if before == to
-
error = "#{message}.\n#{error}" if message
-
assert_not_equal before, after, error
-
-
unless to == UNTRACKED
-
error = "Expected change to #{to}\n"
-
error = "#{message}.\n#{error}" if message
-
assert to === after, error
-
end
-
-
retval
-
end
-
-
# Assertion that the result of evaluating an expression is not changed before
-
# and after invoking the passed in block.
-
#
-
# assert_no_changes 'Status.all_good?' do
-
# post :create, params: { status: { ok: true } }
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
-
# post :create, params: { status: { ok: false } }
-
# end
-
23
def assert_no_changes(expression, message = nil, &block)
-
exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
-
-
before = exp.call
-
retval = assert_nothing_raised(&block)
-
after = exp.call
-
-
error = "#{expression.inspect} changed"
-
error = "#{message}.\n#{error}" if message
-
-
if before.nil?
-
assert_nil after, error
-
else
-
assert_equal before, after, error
-
end
-
-
retval
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
gem "minitest"
-
-
24
require "minitest"
-
-
24
Minitest.autorun
-
# frozen_string_literal: true
-
-
23
require "active_support/concern"
-
23
require "active_support/inflector"
-
-
23
module ActiveSupport
-
23
module Testing
-
# Resolves a constant from a minitest spec name.
-
#
-
# Given the following spec-style test:
-
#
-
# describe WidgetsController, :index do
-
# describe "authenticated user" do
-
# describe "returns widgets" do
-
# it "has a controller that exists" do
-
# assert_kind_of WidgetsController, @controller
-
# end
-
# end
-
# end
-
# end
-
#
-
# The test will have the following name:
-
#
-
# "WidgetsController::index::authenticated user::returns widgets"
-
#
-
# The constant WidgetsController can be resolved from the name.
-
# The following code will resolve the constant:
-
#
-
# controller = determine_constant_from_test_name(name) do |constant|
-
# Class === constant && constant < ::ActionController::Metal
-
# end
-
23
module ConstantLookup
-
23
extend ::ActiveSupport::Concern
-
-
23
module ClassMethods # :nodoc:
-
23
def determine_constant_from_test_name(test_name)
-
names = test_name.split "::"
-
while names.size > 0 do
-
names.last.sub!(/Test$/, "")
-
begin
-
constant = names.join("::").safe_constantize
-
break(constant) if yield(constant)
-
ensure
-
names.pop
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport
-
23
module Testing
-
23
module Declarative
-
23
unless defined?(Spec)
-
# Helper to define a test method using a String. Under the hood, it replaces
-
# spaces with underscores and defines the test method.
-
#
-
# test "verify something" do
-
# ...
-
# end
-
23
def test(name, &block)
-
526
test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym
-
526
defined = method_defined? test_name
-
526
raise "#{test_name} is already defined in #{self}" if defined
-
526
if block_given?
-
526
define_method(test_name, &block)
-
else
-
define_method(test_name) do
-
flunk "No implementation provided for #{name}"
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/deprecation"
-
-
23
module ActiveSupport
-
23
module Testing
-
23
module Deprecation #:nodoc:
-
23
def assert_deprecated(match = nil, deprecator = nil, &block)
-
result, warnings = collect_deprecations(deprecator, &block)
-
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
-
if match
-
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
-
assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
-
end
-
result
-
end
-
-
23
def assert_not_deprecated(deprecator = nil, &block)
-
result, deprecations = collect_deprecations(deprecator, &block)
-
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
-
result
-
end
-
-
23
def collect_deprecations(deprecator = nil)
-
deprecator ||= ActiveSupport::Deprecation
-
old_behavior = deprecator.behavior
-
deprecations = []
-
deprecator.behavior = Proc.new do |message, callstack|
-
deprecations << message
-
end
-
result = yield
-
[result, deprecations]
-
ensure
-
deprecator.behavior = old_behavior
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/concern"
-
-
23
module ActiveSupport
-
23
module Testing
-
# Adds simple access to sample files called file fixtures.
-
# File fixtures are normal files stored in
-
# <tt>ActiveSupport::TestCase.file_fixture_path</tt>.
-
#
-
# File fixtures are represented as +Pathname+ objects.
-
# This makes it easy to extract specific information:
-
#
-
# file_fixture("example.txt").read # get the file's content
-
# file_fixture("example.mp3").size # get the file size
-
23
module FileFixtures
-
23
extend ActiveSupport::Concern
-
-
23
included do
-
23
class_attribute :file_fixture_path, instance_writer: false
-
end
-
-
# Returns a +Pathname+ to the fixture file named +fixture_name+.
-
#
-
# Raises +ArgumentError+ if +fixture_name+ can't be found.
-
23
def file_fixture(fixture_name)
-
path = Pathname.new(File.join(file_fixture_path, fixture_name))
-
-
if path.exist?
-
path
-
else
-
msg = "the directory '%s' does not contain a file named '%s'"
-
raise ArgumentError, msg % [file_fixture_path, fixture_name]
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport
-
23
module Testing
-
23
module Isolation
-
23
require "thread"
-
-
23
def self.included(klass) #:nodoc:
-
4
klass.class_eval do
-
4
parallelize_me!
-
end
-
end
-
-
23
def self.forking_env?
-
23
!ENV["NO_FORK"] && Process.respond_to?(:fork)
-
end
-
-
23
def run
-
serialized = run_in_isolation do
-
super
-
end
-
-
Marshal.load(serialized)
-
end
-
-
23
module Forking
-
23
def run_in_isolation(&blk)
-
read, write = IO.pipe
-
read.binmode
-
write.binmode
-
-
pid = fork do
-
read.close
-
yield
-
begin
-
if error?
-
failures.map! { |e|
-
begin
-
Marshal.dump e
-
e
-
rescue TypeError
-
ex = Exception.new e.message
-
ex.set_backtrace e.backtrace
-
Minitest::UnexpectedError.new ex
-
end
-
}
-
end
-
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
-
result = Marshal.dump(test_result)
-
end
-
-
write.puts [result].pack("m")
-
exit!
-
end
-
-
write.close
-
result = read.read
-
Process.wait2(pid)
-
result.unpack1("m")
-
end
-
end
-
-
23
module Subprocess
-
23
ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
-
-
# Crazy H4X to get this working in windows / jruby with
-
# no forking.
-
23
def run_in_isolation(&blk)
-
require "tempfile"
-
-
if ENV["ISOLATION_TEST"]
-
yield
-
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
-
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
-
file.puts [Marshal.dump(test_result)].pack("m")
-
end
-
exit!
-
else
-
Tempfile.open("isolation") do |tmpfile|
-
env = {
-
"ISOLATION_TEST" => self.class.name,
-
"ISOLATION_OUTPUT" => tmpfile.path
-
}
-
-
test_opts = "-n#{self.class.name}##{name}"
-
-
load_path_args = []
-
$-I.each do |p|
-
load_path_args << "-I"
-
load_path_args << File.expand_path(p)
-
end
-
-
child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
-
-
begin
-
Process.wait(child.pid)
-
rescue Errno::ECHILD # The child process may exit before we wait
-
nil
-
end
-
-
return tmpfile.read.unpack1("m")
-
end
-
end
-
end
-
end
-
-
23
include forking_env? ? Forking : Subprocess
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require "minitest/mock"
-
-
24
module ActiveSupport
-
24
module Testing
-
24
module MethodCallAssertions # :nodoc:
-
24
private
-
24
def assert_called(object, method_name, message = nil, times: 1, returns: nil)
-
times_called = 0
-
-
object.stub(method_name, proc { times_called += 1; returns }) { yield }
-
-
error = "Expected #{method_name} to be called #{times} times, " \
-
"but was called #{times_called} times"
-
error = "#{message}.\n#{error}" if message
-
assert_equal times, times_called, error
-
end
-
-
24
def assert_called_with(object, method_name, args, returns: nil)
-
mock = Minitest::Mock.new
-
-
if args.all? { |arg| arg.is_a?(Array) }
-
args.each { |arg| mock.expect(:call, returns, arg) }
-
else
-
mock.expect(:call, returns, args)
-
end
-
-
object.stub(method_name, mock) { yield }
-
-
mock.verify
-
end
-
-
24
def assert_not_called(object, method_name, message = nil, &block)
-
assert_called(object, method_name, message, times: 0, &block)
-
end
-
-
24
def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
-
times_called = 0
-
klass.define_method("stubbed_#{method_name}") do |*|
-
times_called += 1
-
-
returns
-
end
-
-
klass.alias_method "original_#{method_name}", method_name
-
klass.alias_method method_name, "stubbed_#{method_name}"
-
-
yield
-
-
error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
-
error = "#{message}.\n#{error}" if message
-
-
assert_equal times, times_called, error
-
ensure
-
klass.alias_method method_name, "original_#{method_name}"
-
klass.undef_method "original_#{method_name}"
-
klass.undef_method "stubbed_#{method_name}"
-
end
-
-
24
def assert_not_called_on_instance_of(klass, method_name, message = nil, &block)
-
assert_called_on_instance_of(klass, method_name, message, times: 0, &block)
-
end
-
-
24
def stub_any_instance(klass, instance: klass.new)
-
klass.stub(:new, instance) { yield instance }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "drb"
-
23
require "drb/unix" unless Gem.win_platform?
-
23
require "active_support/core_ext/module/attribute_accessors"
-
23
require "active_support/testing/parallelization/server"
-
23
require "active_support/testing/parallelization/worker"
-
-
23
module ActiveSupport
-
23
module Testing
-
23
class Parallelization # :nodoc:
-
23
@@after_fork_hooks = []
-
-
23
def self.after_fork_hook(&blk)
-
@@after_fork_hooks << blk
-
end
-
-
23
cattr_reader :after_fork_hooks
-
-
23
@@run_cleanup_hooks = []
-
-
23
def self.run_cleanup_hook(&blk)
-
@@run_cleanup_hooks << blk
-
end
-
-
23
cattr_reader :run_cleanup_hooks
-
-
23
def initialize(worker_count)
-
23
@worker_count = worker_count
-
23
@queue_server = Server.new
-
23
@worker_pool = []
-
23
@url = DRb.start_service("drbunix:", @queue_server).uri
-
end
-
-
23
def start
-
23
@worker_pool = @worker_count.times.map do |worker|
-
46
Worker.new(worker, @url).start
-
end
-
end
-
-
23
def <<(work)
-
5804
@queue_server << work
-
end
-
-
23
def shutdown
-
23
@queue_server.shutdown
-
69
@worker_pool.each { |pid| Process.waitpid pid }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "drb"
-
23
require "drb/unix" unless Gem.win_platform?
-
-
23
module ActiveSupport
-
23
module Testing
-
23
class Parallelization # :nodoc:
-
23
class Server
-
23
include DRb::DRbUndumped
-
-
23
def initialize
-
23
@queue = Queue.new
-
23
@active_workers = Concurrent::Map.new
-
23
@in_flight = Concurrent::Map.new
-
end
-
-
23
def record(reporter, result)
-
5804
raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown)
-
-
5804
@in_flight.delete([result.klass, result.name])
-
-
5804
reporter.synchronize do
-
5804
reporter.record(result)
-
end
-
end
-
-
23
def <<(o)
-
5804
o[2] = DRbObject.new(o[2]) if o
-
5804
@queue << o
-
end
-
-
23
def pop
-
5850
if test = @queue.pop
-
5804
@in_flight[[test[0].to_s, test[1]]] = test
-
5804
test
-
end
-
end
-
-
23
def start_worker(worker_id)
-
46
@active_workers[worker_id] = true
-
end
-
-
23
def stop_worker(worker_id)
-
46
@active_workers.delete(worker_id)
-
end
-
-
23
def active_workers?
-
91
@active_workers.size > 0
-
end
-
-
23
def shutdown
-
# Wait for initial queue to drain
-
23
while @queue.length != 0
-
885
sleep 0.1
-
end
-
-
23
@queue.close
-
-
# Wait until all workers have finished
-
23
while active_workers?
-
68
sleep 0.1
-
end
-
-
23
@in_flight.values.each do |(klass, name, reporter)|
-
result = Minitest::Result.from(klass.new(name))
-
error = RuntimeError.new("result not reported")
-
error.set_backtrace([""])
-
result.failures << Minitest::UnexpectedError.new(error)
-
reporter.synchronize do
-
reporter.record(result)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport
-
23
module Testing
-
23
class Parallelization # :nodoc:
-
23
class Worker
-
23
def initialize(number, url)
-
46
@id = SecureRandom.uuid
-
46
@number = number
-
46
@url = url
-
46
@setup_exception = nil
-
end
-
-
23
def start
-
46
fork do
-
set_process_title("(starting)")
-
-
DRb.stop_service
-
-
@queue = DRbObject.new_with_uri(@url)
-
@queue.start_worker(@id)
-
-
begin
-
after_fork
-
rescue => @setup_exception; end
-
-
work_from_queue
-
ensure
-
set_process_title("(stopping)")
-
-
run_cleanup
-
@queue.stop_worker(@id)
-
end
-
end
-
-
23
def work_from_queue
-
while job = @queue.pop
-
perform_job(job)
-
end
-
end
-
-
23
def perform_job(job)
-
klass = job[0]
-
method = job[1]
-
reporter = job[2]
-
-
set_process_title("#{klass}##{method}")
-
-
result = klass.with_info_handler reporter do
-
Minitest.run_one_method(klass, method)
-
end
-
-
safe_record(reporter, result)
-
end
-
-
23
def safe_record(reporter, result)
-
add_setup_exception(result) if @setup_exception
-
-
begin
-
@queue.record(reporter, result)
-
rescue DRb::DRbConnError
-
result.failures.map! do |failure|
-
if failure.respond_to?(:error)
-
# minitest >5.14.0
-
error = DRb::DRbRemoteError.new(failure.error)
-
else
-
error = DRb::DRbRemoteError.new(failure.exception)
-
end
-
Minitest::UnexpectedError.new(error)
-
end
-
@queue.record(reporter, result)
-
end
-
-
set_process_title("(idle)")
-
end
-
-
23
def after_fork
-
Parallelization.after_fork_hooks.each do |cb|
-
cb.call(@number)
-
end
-
end
-
-
23
def run_cleanup
-
Parallelization.run_cleanup_hooks.each do |cb|
-
cb.call(@number)
-
end
-
end
-
-
23
private
-
23
def add_setup_exception(result)
-
result.failures.prepend Minitest::UnexpectedError.new(@setup_exception)
-
end
-
-
23
def set_process_title(status)
-
Process.setproctitle("Rails test worker #{@number} - #{status}")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/callbacks"
-
-
23
module ActiveSupport
-
23
module Testing
-
# Adds support for +setup+ and +teardown+ callbacks.
-
# These callbacks serve as a replacement to overwriting the
-
# <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase.
-
#
-
# class ExampleTest < ActiveSupport::TestCase
-
# setup do
-
# # ...
-
# end
-
#
-
# teardown do
-
# # ...
-
# end
-
# end
-
23
module SetupAndTeardown
-
23
def self.prepended(klass)
-
23
klass.include ActiveSupport::Callbacks
-
23
klass.define_callbacks :setup, :teardown
-
23
klass.extend ClassMethods
-
end
-
-
23
module ClassMethods
-
# Add a callback, which runs before <tt>TestCase#setup</tt>.
-
23
def setup(*args, &block)
-
25
set_callback(:setup, :before, *args, &block)
-
end
-
-
# Add a callback, which runs after <tt>TestCase#teardown</tt>.
-
23
def teardown(*args, &block)
-
14
set_callback(:teardown, :after, *args, &block)
-
end
-
end
-
-
23
def before_setup # :nodoc:
-
super
-
run_callbacks :setup
-
end
-
-
23
def after_teardown # :nodoc:
-
begin
-
run_callbacks :teardown
-
rescue => e
-
self.failures << Minitest::UnexpectedError.new(e)
-
end
-
-
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Stream #:nodoc:
-
1
private
-
1
def silence_stream(stream)
-
old_stream = stream.dup
-
stream.reopen(IO::NULL)
-
stream.sync = true
-
yield
-
ensure
-
stream.reopen(old_stream)
-
old_stream.close
-
end
-
-
1
def quietly
-
silence_stream(STDOUT) do
-
silence_stream(STDERR) do
-
yield
-
end
-
end
-
end
-
-
1
def capture(stream)
-
stream = stream.to_s
-
captured_stream = Tempfile.new(stream)
-
stream_io = eval("$#{stream}")
-
origin_stream = stream_io.dup
-
stream_io.reopen(captured_stream)
-
-
yield
-
-
stream_io.rewind
-
captured_stream.read
-
ensure
-
captured_stream.close
-
captured_stream.unlink
-
stream_io.reopen(origin_stream)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
module ActiveSupport
-
23
module Testing
-
# Logs a "PostsControllerTest: test name" heading before each test to
-
# make test.log easier to search and follow along with.
-
23
module TaggedLogging #:nodoc:
-
23
attr_writer :tagged_logger
-
-
23
def before_setup
-
if tagged_logger && tagged_logger.info?
-
heading = "#{self.class}: #{name}"
-
divider = "-" * heading.size
-
tagged_logger.info divider
-
tagged_logger.info heading
-
tagged_logger.info divider
-
end
-
super
-
end
-
-
23
private
-
23
def tagged_logger
-
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/module/redefine_method"
-
23
require "active_support/core_ext/time/calculations"
-
23
require "concurrent/map"
-
-
23
module ActiveSupport
-
23
module Testing
-
23
class SimpleStubs # :nodoc:
-
23
Stub = Struct.new(:object, :method_name, :original_method)
-
-
23
def initialize
-
@stubs = Concurrent::Map.new { |h, k| h[k] = {} }
-
end
-
-
23
def stub_object(object, method_name, &block)
-
if stub = stubbing(object, method_name)
-
unstub_object(stub)
-
end
-
-
new_name = "__simple_stub__#{method_name}"
-
-
@stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
-
-
object.singleton_class.alias_method new_name, method_name
-
object.define_singleton_method(method_name, &block)
-
end
-
-
23
def unstub_all!
-
@stubs.each_value do |object_stubs|
-
object_stubs.each_value do |stub|
-
unstub_object(stub)
-
end
-
end
-
@stubs.clear
-
end
-
-
23
def stubbing(object, method_name)
-
@stubs[object.object_id][method_name]
-
end
-
-
23
def stubbed?
-
!@stubs.empty?
-
end
-
-
23
private
-
23
def unstub_object(stub)
-
singleton_class = stub.object.singleton_class
-
singleton_class.silence_redefinition_of_method stub.method_name
-
singleton_class.alias_method stub.method_name, stub.original_method
-
singleton_class.undef_method stub.original_method
-
end
-
end
-
-
# Contains helpers that help you test passage of time.
-
23
module TimeHelpers
-
23
def after_teardown
-
travel_back
-
super
-
end
-
-
# Changes current time to the time in the future or in the past by a given time difference by
-
# stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed
-
# at the end of the test.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day
-
# Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# Date.current # => Sun, 10 Nov 2013
-
# DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day do
-
# User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
23
def travel(duration, &block)
-
travel_to Time.now + duration, &block
-
end
-
-
# Changes current time to the given time by stubbing +Time.now+,
-
# +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
-
# The stubs are automatically removed at the end of the test.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# Date.current # => Wed, 24 Nov 2004
-
# DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
-
#
-
# Dates are taken as their timestamp at the beginning of the day in the
-
# application time zone. <tt>Time.current</tt> returns said timestamp,
-
# and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
-
# <tt>Date.current</tt> returns a date equal to the argument, and
-
# <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
-
# be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
-
# or <tt>Date.today</tt>, in order to honor the application time zone
-
# please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
-
#
-
# Note that the usec for the time passed will be set to 0 to prevent rounding
-
# errors with external services, like MySQL (which will round instead of floor,
-
# leading to off-by-one-second errors).
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) do
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
23
def travel_to(date_or_time)
-
if block_given? && simple_stubs.stubbing(Time, :now)
-
travel_to_nested_block_call = <<~MSG
-
-
Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.
-
-
Instead of:
-
-
travel_to 2.days.from_now do
-
# 2 days from today
-
travel_to 3.days.from_now do
-
# 5 days from today
-
end
-
end
-
-
preferred way to achieve above is:
-
-
travel 2.days do
-
# 2 days from today
-
end
-
-
travel 5.days do
-
# 5 days from today
-
end
-
-
MSG
-
raise travel_to_nested_block_call
-
end
-
-
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
-
now = date_or_time.midnight.to_time
-
else
-
now = date_or_time.to_time.change(usec: 0)
-
end
-
-
simple_stubs.stub_object(Time, :now) { at(now.to_i) }
-
simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
-
simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }
-
-
if block_given?
-
begin
-
yield
-
ensure
-
travel_back
-
end
-
end
-
end
-
-
# Returns the current time back to its original state, by removing the stubs added by
-
# +travel+, +travel_to+, and +freeze_time+.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
#
-
# travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
#
-
# travel_back
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
#
-
# This method also accepts a block, which brings the stubs back at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
#
-
# travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
#
-
# travel_back do
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# end
-
#
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
23
def travel_back
-
stubbed_time = Time.current if block_given? && simple_stubs.stubbed?
-
-
simple_stubs.unstub_all!
-
yield if block_given?
-
ensure
-
travel_to stubbed_time if stubbed_time
-
end
-
23
alias_method :unfreeze_time, :travel_back
-
-
# Calls +travel_to+ with +Time.now+.
-
#
-
# Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
-
# freeze_time
-
# sleep(1)
-
# Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
-
# freeze_time do
-
# sleep(1)
-
# User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
-
# end
-
# Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00
-
23
def freeze_time(&block)
-
travel_to Time.now, &block
-
end
-
-
23
private
-
23
def simple_stubs
-
@simple_stubs ||= SimpleStubs.new
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveSupport
-
2
autoload :Duration, "active_support/duration"
-
2
autoload :TimeWithZone, "active_support/time_with_zone"
-
2
autoload :TimeZone, "active_support/values/time_zone"
-
end
-
-
2
require "date"
-
2
require "time"
-
-
2
require "active_support/core_ext/time"
-
2
require "active_support/core_ext/date"
-
2
require "active_support/core_ext/date_time"
-
-
2
require "active_support/core_ext/integer/time"
-
2
require "active_support/core_ext/numeric/time"
-
-
2
require "active_support/core_ext/string/conversions"
-
2
require "active_support/core_ext/string/zones"
-
# frozen_string_literal: true
-
-
23
require "active_support/duration"
-
23
require "active_support/values/time_zone"
-
23
require "active_support/core_ext/object/acts_like"
-
23
require "active_support/core_ext/date_and_time/compatibility"
-
-
23
module ActiveSupport
-
# A Time-like class that can represent a time in any time zone. Necessary
-
# because standard Ruby Time instances are limited to UTC and the
-
# system's <tt>ENV['TZ']</tt> zone.
-
#
-
# You shouldn't ever need to create a TimeWithZone instance directly via +new+.
-
# Instead use methods +local+, +parse+, +at+ and +now+ on TimeZone instances,
-
# and +in_time_zone+ on Time and DateTime instances.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00
-
# Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00
-
# Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00
-
# Time.zone.now # => Sun, 18 May 2008 13:07:55.754107581 EDT -04:00
-
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00
-
#
-
# See Time and TimeZone for further documentation of these methods.
-
#
-
# TimeWithZone instances implement the same API as Ruby Time instances, so
-
# that Time and TimeWithZone instances are interchangeable.
-
#
-
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25.031505668 EDT -04:00
-
# t.hour # => 13
-
# t.dst? # => true
-
# t.utc_offset # => -14400
-
# t.zone # => "EDT"
-
# t.to_s(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400"
-
# t + 1.day # => Mon, 19 May 2008 13:27:25.031505668 EDT -04:00
-
# t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00.000000000 EST -05:00
-
# t > Time.utc(1999) # => true
-
# t.is_a?(Time) # => true
-
# t.is_a?(ActiveSupport::TimeWithZone) # => true
-
23
class TimeWithZone
-
# Report class name as 'Time' to thwart type checking.
-
23
def self.name
-
"Time"
-
end
-
-
23
PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N" }
-
23
PRECISIONS[0] = "%FT%T"
-
-
23
include Comparable, DateAndTime::Compatibility
-
23
attr_reader :time_zone
-
-
23
def initialize(utc_time, time_zone, local_time = nil, period = nil)
-
@utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
-
@time_zone, @time = time_zone, local_time
-
@period = @utc ? period : get_period_and_ensure_valid_local_time(period)
-
end
-
-
# Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
-
23
def time
-
@time ||= incorporate_utc_offset(@utc, utc_offset)
-
end
-
-
# Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
-
23
def utc
-
@utc ||= incorporate_utc_offset(@time, -utc_offset)
-
end
-
23
alias_method :comparable_time, :utc
-
23
alias_method :getgm, :utc
-
23
alias_method :getutc, :utc
-
23
alias_method :gmtime, :utc
-
-
# Returns the underlying TZInfo::TimezonePeriod.
-
23
def period
-
@period ||= time_zone.period_for_utc(@utc)
-
end
-
-
# Returns the simultaneous time in <tt>Time.zone</tt>, or the specified zone.
-
23
def in_time_zone(new_zone = ::Time.zone)
-
return self if time_zone == new_zone
-
utc.in_time_zone(new_zone)
-
end
-
-
# Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
-
23
def localtime(utc_offset = nil)
-
utc.getlocal(utc_offset)
-
end
-
23
alias_method :getlocal, :localtime
-
-
# Returns true if the current time is within Daylight Savings Time for the
-
# specified time zone.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# Time.zone.parse("2012-5-30").dst? # => true
-
# Time.zone.parse("2012-11-30").dst? # => false
-
23
def dst?
-
period.dst?
-
end
-
23
alias_method :isdst, :dst?
-
-
# Returns true if the current time zone is set to UTC.
-
#
-
# Time.zone = 'UTC' # => 'UTC'
-
# Time.zone.now.utc? # => true
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# Time.zone.now.utc? # => false
-
23
def utc?
-
zone == "UTC" || zone == "UCT"
-
end
-
23
alias_method :gmt?, :utc?
-
-
# Returns the offset from current time to UTC time in seconds.
-
23
def utc_offset
-
period.observed_utc_offset
-
end
-
23
alias_method :gmt_offset, :utc_offset
-
23
alias_method :gmtoff, :utc_offset
-
-
# Returns a formatted string of the offset from UTC, or an alternative
-
# string if the time zone is already UTC.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
-
# Time.zone.now.formatted_offset(true) # => "-05:00"
-
# Time.zone.now.formatted_offset(false) # => "-0500"
-
# Time.zone = 'UTC' # => "UTC"
-
# Time.zone.now.formatted_offset(true, "0") # => "0"
-
23
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Returns the time zone abbreviation.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
-
# Time.zone.now.zone # => "EST"
-
23
def zone
-
period.abbreviation
-
end
-
-
# Returns a string of the object's date, time, zone, and offset from UTC.
-
#
-
# Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00"
-
23
def inspect
-
"#{time.strftime('%a, %d %b %Y %H:%M:%S.%9N')} #{zone} #{formatted_offset}"
-
end
-
-
# Returns a string of the object's date and time in the ISO 8601 standard
-
# format.
-
#
-
# Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
-
23
def xmlschema(fraction_digits = 0)
-
"#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z')}"
-
end
-
23
alias_method :iso8601, :xmlschema
-
23
alias_method :rfc3339, :xmlschema
-
-
# Coerces time to a string for JSON encoding. The default format is ISO 8601.
-
# You can get %Y/%m/%d %H:%M:%S +offset style by setting
-
# <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
-
# to +false+.
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
-
# Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
-
# # => "2005-02-01T05:15:10.000-10:00"
-
#
-
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
-
# Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
-
# # => "2005/02/01 05:15:10 -1000"
-
23
def as_json(options = nil)
-
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
-
xmlschema(ActiveSupport::JSON::Encoding.time_precision)
-
else
-
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
-
end
-
end
-
-
23
def init_with(coder) #:nodoc:
-
initialize(coder["utc"], coder["zone"], coder["time"])
-
end
-
-
23
def encode_with(coder) #:nodoc:
-
coder.tag = "!ruby/object:ActiveSupport::TimeWithZone"
-
coder.map = { "utc" => utc, "zone" => time_zone, "time" => time }
-
end
-
-
# Returns a string of the object's date and time in the format used by
-
# HTTP requests.
-
#
-
# Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT"
-
23
def httpdate
-
utc.httpdate
-
end
-
-
# Returns a string of the object's date and time in the RFC 2822 standard
-
# format.
-
#
-
# Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000"
-
23
def rfc2822
-
to_s(:rfc822)
-
end
-
23
alias_method :rfc822, :rfc2822
-
-
# Returns a string of the object's date and time.
-
# Accepts an optional <tt>format</tt>:
-
# * <tt>:default</tt> - default value, mimics Ruby Time#to_s format.
-
# * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db).
-
# * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb.
-
23
def to_s(format = :default)
-
if format == :db
-
utc.to_s(format)
-
elsif formatter = ::Time::DATE_FORMATS[format]
-
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
-
else
-
"#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format
-
end
-
end
-
23
alias_method :to_formatted_s, :to_s
-
-
# Replaces <tt>%Z</tt> directive with +zone before passing to Time#strftime,
-
# so that zone information is correct.
-
23
def strftime(format)
-
format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}")
-
getlocal(utc_offset).strftime(format)
-
end
-
-
# Use the time in UTC for comparisons.
-
23
def <=>(other)
-
utc <=> other
-
end
-
23
alias_method :before?, :<
-
23
alias_method :after?, :>
-
-
# Returns true if the current object's time is within the specified
-
# +min+ and +max+ time.
-
23
def between?(min, max)
-
utc.between?(min, max)
-
end
-
-
# Returns true if the current object's time is in the past.
-
23
def past?
-
utc.past?
-
end
-
-
# Returns true if the current object's time falls within
-
# the current day.
-
23
def today?
-
time.today?
-
end
-
-
# Returns true if the current object's time falls within
-
# the next day (tomorrow).
-
23
def tomorrow?
-
time.tomorrow?
-
end
-
23
alias :next_day? :tomorrow?
-
-
# Returns true if the current object's time falls within
-
# the previous day (yesterday).
-
23
def yesterday?
-
time.yesterday?
-
end
-
23
alias :prev_day? :yesterday?
-
-
# Returns true if the current object's time is in the future.
-
23
def future?
-
utc.future?
-
end
-
-
# Returns +true+ if +other+ is equal to current object.
-
23
def eql?(other)
-
other.eql?(utc)
-
end
-
-
23
def hash
-
utc.hash
-
end
-
-
# Adds an interval of time to the current object's time and returns that
-
# value as a new TimeWithZone object.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00
-
# now + 1000 # => Sun, 02 Nov 2014 01:43:08.725182881 EDT -04:00
-
#
-
# If we're adding a Duration of variable length (i.e., years, months, days),
-
# move forward from #time, otherwise move forward from #utc, for accuracy
-
# when moving across DST boundaries.
-
#
-
# For instance, a time + 24.hours will advance exactly 24 hours, while a
-
# time + 1.day will advance 23-25 hours, depending on the day.
-
#
-
# now + 24.hours # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00
-
# now + 1.day # => Mon, 03 Nov 2014 01:26:28.725182881 EST -05:00
-
23
def +(other)
-
if duration_of_variable_length?(other)
-
method_missing(:+, other)
-
else
-
result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
-
result.in_time_zone(time_zone)
-
end
-
end
-
23
alias_method :since, :+
-
23
alias_method :in, :+
-
-
# Subtracts an interval of time and returns a new TimeWithZone object unless
-
# the other value `acts_like?` time. Then it will return a Float of the difference
-
# between the two times that represents the difference between the current
-
# object's time and the +other+ time.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00
-
# now - 1000 # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00
-
#
-
# If subtracting a Duration of variable length (i.e., years, months, days),
-
# move backward from #time, otherwise move backward from #utc, for accuracy
-
# when moving across DST boundaries.
-
#
-
# For instance, a time - 24.hours will go subtract exactly 24 hours, while a
-
# time - 1.day will subtract 23-25 hours, depending on the day.
-
#
-
# now - 24.hours # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00
-
# now - 1.day # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00
-
#
-
# If both the TimeWithZone object and the other value act like Time, a Float
-
# will be returned.
-
#
-
# Time.zone.now - 1.day.ago # => 86399.999967
-
#
-
23
def -(other)
-
if other.acts_like?(:time)
-
to_time - other.to_time
-
elsif duration_of_variable_length?(other)
-
method_missing(:-, other)
-
else
-
result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other)
-
result.in_time_zone(time_zone)
-
end
-
end
-
-
# Subtracts an interval of time from the current object's time and returns
-
# the result as a new TimeWithZone object.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00
-
# now.ago(1000) # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00
-
#
-
# If we're subtracting a Duration of variable length (i.e., years, months,
-
# days), move backward from #time, otherwise move backward from #utc, for
-
# accuracy when moving across DST boundaries.
-
#
-
# For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours,
-
# while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on
-
# the day.
-
#
-
# now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00
-
# now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00
-
23
def ago(other)
-
since(-other)
-
end
-
-
# Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have
-
# been changed according to the +options+ parameter. The time options (<tt>:hour</tt>,
-
# <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly,
-
# so if only the hour is passed, then minute, sec, usec and nsec is set to 0. If the
-
# hour and minute is passed, then sec, usec and nsec is set to 0. The +options+
-
# parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
-
# <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>,
-
# <tt>:nsec</tt>, <tt>:offset</tt>, <tt>:zone</tt>. Pass either <tt>:usec</tt>
-
# or <tt>:nsec</tt>, not both. Similarly, pass either <tt>:zone</tt> or
-
# <tt>:offset</tt>, not both.
-
#
-
# t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15.116992711 EST -05:00
-
# t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15.116992711 EST -05:00
-
# t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00.116992711 EST -05:00
-
# t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.116992711 EST -05:00
-
# t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00
-
# t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00
-
23
def change(options)
-
if options[:zone] && options[:offset]
-
raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}"
-
end
-
-
new_time = time.change(options)
-
-
if options[:zone]
-
new_zone = ::Time.find_zone(options[:zone])
-
elsif options[:offset]
-
new_zone = ::Time.find_zone(new_time.utc_offset)
-
end
-
-
new_zone ||= time_zone
-
periods = new_zone.periods_for_local(new_time)
-
-
self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil)
-
end
-
-
# Uses Date to provide precise Time calculations for years, months, and days
-
# according to the proleptic Gregorian calendar. The result is returned as a
-
# new TimeWithZone object.
-
#
-
# The +options+ parameter takes a hash with any of these keys:
-
# <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>,
-
# <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>.
-
#
-
# If advancing by a value of variable length (i.e., years, weeks, months,
-
# days), move forward from #time, otherwise move forward from #utc, for
-
# accuracy when moving across DST boundaries.
-
#
-
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
-
# now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.558049687 EDT -04:00
-
# now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29.558049687 EDT -04:00
-
# now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28.558049687 EDT -04:00
-
# now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28.558049687 EST -05:00
-
# now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28.558049687 EST -05:00
-
# now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28.558049687 EST -05:00
-
# now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28.558049687 EST -05:00
-
# now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28.558049687 EST -05:00
-
23
def advance(options)
-
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
-
# otherwise advance from #utc, for accuracy when moving across DST boundaries
-
if options.values_at(:years, :weeks, :months, :days).any?
-
method_missing(:advance, options)
-
else
-
utc.advance(options).in_time_zone(time_zone)
-
end
-
end
-
-
23
%w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name|
-
299
class_eval <<-EOV, __FILE__, __LINE__ + 1
-
def #{method_name} # def month
-
time.#{method_name} # time.month
-
end # end
-
EOV
-
end
-
-
# Returns Array of parts of Time in sequence of
-
# [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone].
-
#
-
# now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27.485278555 UTC +00:00
-
# now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"]
-
23
def to_a
-
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
-
end
-
-
# Returns the object's date and time as a floating point number of seconds
-
# since the Epoch (January 1, 1970 00:00 UTC).
-
#
-
# Time.zone.now.to_f # => 1417709320.285418
-
23
def to_f
-
utc.to_f
-
end
-
-
# Returns the object's date and time as an integer number of seconds
-
# since the Epoch (January 1, 1970 00:00 UTC).
-
#
-
# Time.zone.now.to_i # => 1417709320
-
23
def to_i
-
utc.to_i
-
end
-
23
alias_method :tv_sec, :to_i
-
-
# Returns the object's date and time as a rational number of seconds
-
# since the Epoch (January 1, 1970 00:00 UTC).
-
#
-
# Time.zone.now.to_r # => (708854548642709/500000)
-
23
def to_r
-
utc.to_r
-
end
-
-
# Returns an instance of DateTime with the timezone's UTC offset
-
#
-
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
-
# Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000
-
23
def to_datetime
-
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
-
end
-
-
# Returns an instance of +Time+, either with the same UTC offset
-
# as +self+ or in the local system timezone depending on the setting
-
# of +ActiveSupport.to_time_preserves_timezone+.
-
23
def to_time
-
if preserve_timezone
-
@to_time_with_instance_offset ||= getlocal(utc_offset)
-
else
-
@to_time_with_system_offset ||= getlocal
-
end
-
end
-
-
# So that +self+ <tt>acts_like?(:time)</tt>.
-
23
def acts_like_time?
-
true
-
end
-
-
# Say we're a Time to thwart type checking.
-
23
def is_a?(klass)
-
klass == ::Time || super
-
end
-
23
alias_method :kind_of?, :is_a?
-
-
# An instance of ActiveSupport::TimeWithZone is never blank
-
23
def blank?
-
false
-
end
-
-
23
def freeze
-
# preload instance variables before freezing
-
period; utc; time; to_datetime; to_time
-
super
-
end
-
-
23
def marshal_dump
-
[utc, time_zone.name, time]
-
end
-
-
23
def marshal_load(variables)
-
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
-
end
-
-
# respond_to_missing? is not called in some cases, such as when type conversion is
-
# performed with Kernel#String
-
23
def respond_to?(sym, include_priv = false)
-
# ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow
-
return false if sym.to_sym == :to_str
-
super
-
end
-
-
# Ensure proxy class responds to all methods that underlying time instance
-
# responds to.
-
23
def respond_to_missing?(sym, include_priv)
-
return false if sym.to_sym == :acts_like_date?
-
time.respond_to?(sym, include_priv)
-
end
-
-
# Send the missing method to +time+ instance, and wrap result in a new
-
# TimeWithZone with the existing +time_zone+.
-
23
def method_missing(sym, *args, &block)
-
wrap_with_time_zone time.__send__(sym, *args, &block)
-
rescue NoMethodError => e
-
raise e, e.message.sub(time.inspect, inspect), e.backtrace
-
end
-
-
23
private
-
23
SECONDS_PER_DAY = 86400
-
-
23
def incorporate_utc_offset(time, offset)
-
if time.kind_of?(Date)
-
time + Rational(offset, SECONDS_PER_DAY)
-
else
-
time + offset
-
end
-
end
-
-
23
def get_period_and_ensure_valid_local_time(period)
-
# we don't want a Time.local instance enforcing its own DST rules as well,
-
# so transfer time values to a utc constructor if necessary
-
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
-
begin
-
period || @time_zone.period_for_local(@time)
-
rescue ::TZInfo::PeriodNotFound
-
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
-
@time += 1.hour
-
retry
-
end
-
end
-
-
23
def transfer_time_values_to_utc_constructor(time)
-
# avoid creating another Time object if possible
-
return time if time.instance_of?(::Time) && time.utc?
-
::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
-
end
-
-
23
def duration_of_variable_length?(obj)
-
ActiveSupport::Duration === obj && obj.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) }
-
end
-
-
23
def wrap_with_time_zone(time)
-
if time.acts_like?(:time)
-
periods = time_zone.periods_for_local(time)
-
self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil)
-
elsif time.is_a?(Range)
-
wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
-
else
-
time
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "tzinfo"
-
23
require "concurrent/map"
-
-
23
module ActiveSupport
-
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
-
# It allows us to do the following:
-
#
-
# * Limit the set of zones provided by TZInfo to a meaningful subset of 134
-
# zones.
-
# * Retrieve and display zones with a friendlier name
-
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
-
# * Lazily load TZInfo::Timezone instances only when they're needed.
-
# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+,
-
# +parse+, +at+ and +now+ methods.
-
#
-
# If you set <tt>config.time_zone</tt> in the Rails Application, you can
-
# access this TimeZone object via <tt>Time.zone</tt>:
-
#
-
# # application.rb:
-
# class Application < Rails::Application
-
# config.time_zone = 'Eastern Time (US & Canada)'
-
# end
-
#
-
# Time.zone # => #<ActiveSupport::TimeZone:0x514834...>
-
# Time.zone.name # => "Eastern Time (US & Canada)"
-
# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
-
23
class TimeZone
-
# Keys are Rails TimeZone names, values are TZInfo identifiers.
-
23
MAPPING = {
-
"International Date Line West" => "Etc/GMT+12",
-
"Midway Island" => "Pacific/Midway",
-
"American Samoa" => "Pacific/Pago_Pago",
-
"Hawaii" => "Pacific/Honolulu",
-
"Alaska" => "America/Juneau",
-
"Pacific Time (US & Canada)" => "America/Los_Angeles",
-
"Tijuana" => "America/Tijuana",
-
"Mountain Time (US & Canada)" => "America/Denver",
-
"Arizona" => "America/Phoenix",
-
"Chihuahua" => "America/Chihuahua",
-
"Mazatlan" => "America/Mazatlan",
-
"Central Time (US & Canada)" => "America/Chicago",
-
"Saskatchewan" => "America/Regina",
-
"Guadalajara" => "America/Mexico_City",
-
"Mexico City" => "America/Mexico_City",
-
"Monterrey" => "America/Monterrey",
-
"Central America" => "America/Guatemala",
-
"Eastern Time (US & Canada)" => "America/New_York",
-
"Indiana (East)" => "America/Indiana/Indianapolis",
-
"Bogota" => "America/Bogota",
-
"Lima" => "America/Lima",
-
"Quito" => "America/Lima",
-
"Atlantic Time (Canada)" => "America/Halifax",
-
"Caracas" => "America/Caracas",
-
"La Paz" => "America/La_Paz",
-
"Santiago" => "America/Santiago",
-
"Newfoundland" => "America/St_Johns",
-
"Brasilia" => "America/Sao_Paulo",
-
"Buenos Aires" => "America/Argentina/Buenos_Aires",
-
"Montevideo" => "America/Montevideo",
-
"Georgetown" => "America/Guyana",
-
"Puerto Rico" => "America/Puerto_Rico",
-
"Greenland" => "America/Godthab",
-
"Mid-Atlantic" => "Atlantic/South_Georgia",
-
"Azores" => "Atlantic/Azores",
-
"Cape Verde Is." => "Atlantic/Cape_Verde",
-
"Dublin" => "Europe/Dublin",
-
"Edinburgh" => "Europe/London",
-
"Lisbon" => "Europe/Lisbon",
-
"London" => "Europe/London",
-
"Casablanca" => "Africa/Casablanca",
-
"Monrovia" => "Africa/Monrovia",
-
"UTC" => "Etc/UTC",
-
"Belgrade" => "Europe/Belgrade",
-
"Bratislava" => "Europe/Bratislava",
-
"Budapest" => "Europe/Budapest",
-
"Ljubljana" => "Europe/Ljubljana",
-
"Prague" => "Europe/Prague",
-
"Sarajevo" => "Europe/Sarajevo",
-
"Skopje" => "Europe/Skopje",
-
"Warsaw" => "Europe/Warsaw",
-
"Zagreb" => "Europe/Zagreb",
-
"Brussels" => "Europe/Brussels",
-
"Copenhagen" => "Europe/Copenhagen",
-
"Madrid" => "Europe/Madrid",
-
"Paris" => "Europe/Paris",
-
"Amsterdam" => "Europe/Amsterdam",
-
"Berlin" => "Europe/Berlin",
-
"Bern" => "Europe/Zurich",
-
"Zurich" => "Europe/Zurich",
-
"Rome" => "Europe/Rome",
-
"Stockholm" => "Europe/Stockholm",
-
"Vienna" => "Europe/Vienna",
-
"West Central Africa" => "Africa/Algiers",
-
"Bucharest" => "Europe/Bucharest",
-
"Cairo" => "Africa/Cairo",
-
"Helsinki" => "Europe/Helsinki",
-
"Kyiv" => "Europe/Kiev",
-
"Riga" => "Europe/Riga",
-
"Sofia" => "Europe/Sofia",
-
"Tallinn" => "Europe/Tallinn",
-
"Vilnius" => "Europe/Vilnius",
-
"Athens" => "Europe/Athens",
-
"Istanbul" => "Europe/Istanbul",
-
"Minsk" => "Europe/Minsk",
-
"Jerusalem" => "Asia/Jerusalem",
-
"Harare" => "Africa/Harare",
-
"Pretoria" => "Africa/Johannesburg",
-
"Kaliningrad" => "Europe/Kaliningrad",
-
"Moscow" => "Europe/Moscow",
-
"St. Petersburg" => "Europe/Moscow",
-
"Volgograd" => "Europe/Volgograd",
-
"Samara" => "Europe/Samara",
-
"Kuwait" => "Asia/Kuwait",
-
"Riyadh" => "Asia/Riyadh",
-
"Nairobi" => "Africa/Nairobi",
-
"Baghdad" => "Asia/Baghdad",
-
"Tehran" => "Asia/Tehran",
-
"Abu Dhabi" => "Asia/Muscat",
-
"Muscat" => "Asia/Muscat",
-
"Baku" => "Asia/Baku",
-
"Tbilisi" => "Asia/Tbilisi",
-
"Yerevan" => "Asia/Yerevan",
-
"Kabul" => "Asia/Kabul",
-
"Ekaterinburg" => "Asia/Yekaterinburg",
-
"Islamabad" => "Asia/Karachi",
-
"Karachi" => "Asia/Karachi",
-
"Tashkent" => "Asia/Tashkent",
-
"Chennai" => "Asia/Kolkata",
-
"Kolkata" => "Asia/Kolkata",
-
"Mumbai" => "Asia/Kolkata",
-
"New Delhi" => "Asia/Kolkata",
-
"Kathmandu" => "Asia/Kathmandu",
-
"Astana" => "Asia/Dhaka",
-
"Dhaka" => "Asia/Dhaka",
-
"Sri Jayawardenepura" => "Asia/Colombo",
-
"Almaty" => "Asia/Almaty",
-
"Novosibirsk" => "Asia/Novosibirsk",
-
"Rangoon" => "Asia/Rangoon",
-
"Bangkok" => "Asia/Bangkok",
-
"Hanoi" => "Asia/Bangkok",
-
"Jakarta" => "Asia/Jakarta",
-
"Krasnoyarsk" => "Asia/Krasnoyarsk",
-
"Beijing" => "Asia/Shanghai",
-
"Chongqing" => "Asia/Chongqing",
-
"Hong Kong" => "Asia/Hong_Kong",
-
"Urumqi" => "Asia/Urumqi",
-
"Kuala Lumpur" => "Asia/Kuala_Lumpur",
-
"Singapore" => "Asia/Singapore",
-
"Taipei" => "Asia/Taipei",
-
"Perth" => "Australia/Perth",
-
"Irkutsk" => "Asia/Irkutsk",
-
"Ulaanbaatar" => "Asia/Ulaanbaatar",
-
"Seoul" => "Asia/Seoul",
-
"Osaka" => "Asia/Tokyo",
-
"Sapporo" => "Asia/Tokyo",
-
"Tokyo" => "Asia/Tokyo",
-
"Yakutsk" => "Asia/Yakutsk",
-
"Darwin" => "Australia/Darwin",
-
"Adelaide" => "Australia/Adelaide",
-
"Canberra" => "Australia/Melbourne",
-
"Melbourne" => "Australia/Melbourne",
-
"Sydney" => "Australia/Sydney",
-
"Brisbane" => "Australia/Brisbane",
-
"Hobart" => "Australia/Hobart",
-
"Vladivostok" => "Asia/Vladivostok",
-
"Guam" => "Pacific/Guam",
-
"Port Moresby" => "Pacific/Port_Moresby",
-
"Magadan" => "Asia/Magadan",
-
"Srednekolymsk" => "Asia/Srednekolymsk",
-
"Solomon Is." => "Pacific/Guadalcanal",
-
"New Caledonia" => "Pacific/Noumea",
-
"Fiji" => "Pacific/Fiji",
-
"Kamchatka" => "Asia/Kamchatka",
-
"Marshall Is." => "Pacific/Majuro",
-
"Auckland" => "Pacific/Auckland",
-
"Wellington" => "Pacific/Auckland",
-
"Nuku'alofa" => "Pacific/Tongatapu",
-
"Tokelau Is." => "Pacific/Fakaofo",
-
"Chatham Is." => "Pacific/Chatham",
-
"Samoa" => "Pacific/Apia"
-
}
-
-
23
UTC_OFFSET_WITH_COLON = "%s%02d:%02d" # :nodoc:
-
23
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") # :nodoc:
-
23
private_constant :UTC_OFFSET_WITH_COLON, :UTC_OFFSET_WITHOUT_COLON
-
-
23
@lazy_zones_map = Concurrent::Map.new
-
23
@country_zones = Concurrent::Map.new
-
-
23
class << self
-
# Assumes self represents an offset from UTC in seconds (as returned from
-
# Time#utc_offset) and turns this into an +HH:MM formatted string.
-
#
-
# ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
-
23
def seconds_to_utc_offset(seconds, colon = true)
-
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
-
sign = (seconds < 0 ? "-" : "+")
-
hours = seconds.abs / 3600
-
minutes = (seconds.abs % 3600) / 60
-
format % [sign, hours, minutes]
-
end
-
-
23
def find_tzinfo(name)
-
151
TZInfo::Timezone.get(MAPPING[name] || name)
-
end
-
-
23
alias_method :create, :new
-
-
# Returns a TimeZone instance with the given name, or +nil+ if no
-
# such TimeZone instance exists. (This exists to support the use of
-
# this class with the +composed_of+ macro.)
-
23
def new(name)
-
self[name]
-
end
-
-
# Returns an array of all TimeZone objects. There are multiple
-
# TimeZone objects per time zone, in many cases, to make it easier
-
# for users to find their own time zone.
-
23
def all
-
1
@zones ||= zones_map.values.sort
-
end
-
-
# Locate a specific time zone object. If the argument is a string, it
-
# is interpreted to mean the name of the timezone to locate. If it is a
-
# numeric value it is either the hour offset, or the second offset, of the
-
# timezone to find. (The first one with that offset will be returned.)
-
# Returns +nil+ if no such time zone is known to the system.
-
23
def [](arg)
-
151
case arg
-
when String
-
151
begin
-
151
@lazy_zones_map[arg] ||= create(arg)
-
rescue TZInfo::InvalidTimezoneIdentifier
-
nil
-
end
-
when Numeric, ActiveSupport::Duration
-
arg *= 3600 if arg.abs <= 13
-
all.find { |z| z.utc_offset == arg.to_i }
-
else
-
raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
-
end
-
end
-
-
# A convenience method for returning a collection of TimeZone objects
-
# for time zones in the USA.
-
23
def us_zones
-
country_zones(:us)
-
end
-
-
# A convenience method for returning a collection of TimeZone objects
-
# for time zones in the country specified by its ISO 3166-1 Alpha2 code.
-
23
def country_zones(country_code)
-
code = country_code.to_s.upcase
-
@country_zones[code] ||= load_country_zones(code)
-
end
-
-
23
def clear #:nodoc:
-
@lazy_zones_map = Concurrent::Map.new
-
@country_zones = Concurrent::Map.new
-
@zones = nil
-
@zones_map = nil
-
end
-
-
23
private
-
23
def load_country_zones(code)
-
country = TZInfo::Country.get(code)
-
country.zone_identifiers.flat_map do |tz_id|
-
if MAPPING.value?(tz_id)
-
MAPPING.inject([]) do |memo, (key, value)|
-
memo << self[key] if value == tz_id
-
memo
-
end
-
else
-
create(tz_id, nil, TZInfo::Timezone.get(tz_id))
-
end
-
end.sort!
-
end
-
-
23
def zones_map
-
1
@zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones|
-
151
timezone = self[name]
-
151
zones[name] = timezone if timezone
-
end
-
end
-
end
-
-
23
include Comparable
-
23
attr_reader :name
-
23
attr_reader :tzinfo
-
-
# Create a new TimeZone object with the given name and offset. The
-
# offset is the number of seconds that this time zone is offset from UTC
-
# (GMT). Seconds were chosen as the offset unit because that is the unit
-
# that Ruby uses to represent time zone offsets (see Time#utc_offset).
-
23
def initialize(name, utc_offset = nil, tzinfo = nil)
-
151
@name = name
-
151
@utc_offset = utc_offset
-
151
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
-
end
-
-
# Returns the offset of this time zone from UTC in seconds.
-
23
def utc_offset
-
1996
@utc_offset || tzinfo&.current_period&.base_utc_offset
-
end
-
-
# Returns a formatted string of the offset from UTC, or an alternative
-
# string if the time zone is already UTC.
-
#
-
# zone = ActiveSupport::TimeZone['Central Time (US & Canada)']
-
# zone.formatted_offset # => "-06:00"
-
# zone.formatted_offset(false) # => "-0600"
-
23
def formatted_offset(colon = true, alternate_utc_string = nil)
-
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
-
end
-
-
# Compare this time zone to the parameter. The two are compared first on
-
# their offsets, and then by name.
-
23
def <=>(zone)
-
998
return unless zone.respond_to? :utc_offset
-
998
result = (utc_offset <=> zone.utc_offset)
-
998
result = (name <=> zone.name) if result == 0
-
998
result
-
end
-
-
# Compare #name and TZInfo identifier to a supplied regexp, returning +true+
-
# if a match is found.
-
23
def =~(re)
-
re === name || re === MAPPING[name]
-
end
-
-
# Compare #name and TZInfo identifier to a supplied regexp, returning +true+
-
# if a match is found.
-
23
def match?(re)
-
(re == name) || (re == MAPPING[name]) ||
-
((Regexp === re) && (re.match?(name) || re.match?(MAPPING[name])))
-
end
-
-
# Returns a textual representation of this time zone.
-
23
def to_s
-
"(GMT#{formatted_offset}) #{name}"
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from given values.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
-
23
def local(*args)
-
time = Time.utc(*args)
-
ActiveSupport::TimeWithZone.new(nil, self, time)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from number of seconds since the Unix epoch.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.utc(2000).to_f # => 946684800.0
-
# Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# A second argument can be supplied to specify sub-second precision.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.at(946684800, 123456.789).nsec # => 123456789
-
23
def at(*args)
-
Time.at(*args).utc.in_time_zone(self)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from an ISO 8601 string.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If the time components are missing then they will be set to zero.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00
-
#
-
# If the string is invalid then an +ArgumentError+ will be raised unlike +parse+
-
# which usually returns +nil+ when given an invalid date string.
-
23
def iso8601(str)
-
parts = Date._iso8601(str)
-
-
raise ArgumentError, "invalid date" if parts.empty?
-
-
time = Time.new(
-
parts.fetch(:year),
-
parts.fetch(:mon),
-
parts.fetch(:mday),
-
parts.fetch(:hour, 0),
-
parts.fetch(:min, 0),
-
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
-
parts.fetch(:offset, 0)
-
)
-
-
if parts[:offset]
-
TimeWithZone.new(time.utc, self)
-
else
-
TimeWithZone.new(nil, self, time)
-
end
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from parsed string.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If upper components are missing from the string, they are supplied from
-
# TimeZone#now:
-
#
-
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
-
#
-
# However, if the date component is not provided, but any other upper
-
# components are supplied, then the day of the month defaults to 1:
-
#
-
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
-
#
-
# If the string is invalid then an +ArgumentError+ could be raised.
-
23
def parse(str, now = now())
-
parts_to_time(Date._parse(str, false), now)
-
end
-
-
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
-
# of +self+ from an RFC 3339 string.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If the time or zone components are missing then an +ArgumentError+ will
-
# be raised. This is much stricter than either +parse+ or +iso8601+ which
-
# allow for missing components.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date
-
23
def rfc3339(str)
-
parts = Date._rfc3339(str)
-
-
raise ArgumentError, "invalid date" if parts.empty?
-
-
time = Time.new(
-
parts.fetch(:year),
-
parts.fetch(:mon),
-
parts.fetch(:mday),
-
parts.fetch(:hour),
-
parts.fetch(:min),
-
parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
-
parts.fetch(:offset)
-
)
-
-
TimeWithZone.new(time.utc, self)
-
end
-
-
# Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
-
#
-
# Assumes that +str+ is a time in the time zone +self+,
-
# unless +format+ includes an explicit time zone.
-
# (This is the same behavior as +parse+.)
-
# In either case, the returned TimeWithZone has the timezone of +self+.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
#
-
# If upper components are missing from the string, they are supplied from
-
# TimeZone#now:
-
#
-
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
# Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
-
#
-
# However, if the date component is not provided, but any other upper
-
# components are supplied, then the day of the month defaults to 1:
-
#
-
# Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
-
23
def strptime(str, format, now = now())
-
parts_to_time(DateTime._strptime(str, format), now)
-
end
-
-
# Returns an ActiveSupport::TimeWithZone instance representing the current
-
# time in the time zone represented by +self+.
-
#
-
# Time.zone = 'Hawaii' # => "Hawaii"
-
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
-
23
def now
-
time_now.utc.in_time_zone(self)
-
end
-
-
# Returns the current date in this time zone.
-
23
def today
-
tzinfo.now.to_date
-
end
-
-
# Returns the next date in this time zone.
-
23
def tomorrow
-
today + 1
-
end
-
-
# Returns the previous date in this time zone.
-
23
def yesterday
-
today - 1
-
end
-
-
# Adjust the given time to the simultaneous time in the time zone
-
# represented by +self+. Returns a local time with the appropriate offset
-
# -- if you want an ActiveSupport::TimeWithZone instance, use
-
# Time#in_time_zone() instead.
-
#
-
# As of tzinfo 2, utc_to_local returns a Time with a non-zero utc_offset.
-
# See the `utc_to_local_returns_utc_offset_times` config for more info.
-
23
def utc_to_local(time)
-
tzinfo.utc_to_local(time).yield_self do |t|
-
ActiveSupport.utc_to_local_returns_utc_offset_times ?
-
t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction)
-
end
-
end
-
-
# Adjust the given time to the simultaneous time in UTC. Returns a
-
# Time.utc() instance.
-
23
def local_to_utc(time, dst = true)
-
tzinfo.local_to_utc(time, dst)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone
-
# instances.
-
23
def period_for_utc(time)
-
tzinfo.period_for_utc(time)
-
end
-
-
# Available so that TimeZone instances respond like TZInfo::Timezone
-
# instances.
-
23
def period_for_local(time, dst = true)
-
tzinfo.period_for_local(time, dst) { |periods| periods.last }
-
end
-
-
23
def periods_for_local(time) #:nodoc:
-
tzinfo.periods_for_local(time)
-
end
-
-
23
def init_with(coder) #:nodoc:
-
initialize(coder["name"])
-
end
-
-
23
def encode_with(coder) #:nodoc:
-
coder.tag = "!ruby/object:#{self.class}"
-
coder.map = { "name" => tzinfo.name }
-
end
-
-
23
private
-
23
def parts_to_time(parts, now)
-
raise ArgumentError, "invalid date" if parts.nil?
-
return if parts.empty?
-
-
if parts[:seconds]
-
time = Time.at(parts[:seconds])
-
else
-
time = Time.new(
-
parts.fetch(:year, now.year),
-
parts.fetch(:mon, now.month),
-
parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
-
parts.fetch(:hour, 0),
-
parts.fetch(:min, 0),
-
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
-
parts.fetch(:offset, 0)
-
)
-
end
-
-
if parts[:offset] || parts[:seconds]
-
TimeWithZone.new(time.utc, self)
-
else
-
TimeWithZone.new(nil, self, time)
-
end
-
end
-
-
23
def time_now
-
Time.now
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
24
require_relative "gem_version"
-
-
24
module ActiveSupport
-
# Returns the version of the currently loaded ActiveSupport as a <tt>Gem::Version</tt>
-
24
def self.version
-
gem_version
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "time"
-
23
require "base64"
-
23
require "bigdecimal"
-
23
require "bigdecimal/util"
-
23
require "active_support/core_ext/module/delegation"
-
23
require "active_support/core_ext/string/inflections"
-
23
require "active_support/core_ext/date_time/calculations"
-
-
23
module ActiveSupport
-
# = XmlMini
-
#
-
# To use the much faster libxml parser:
-
# gem 'libxml-ruby', '=0.9.7'
-
# XmlMini.backend = 'LibXML'
-
23
module XmlMini
-
23
extend self
-
-
# This module decorates files deserialized using Hash.from_xml with
-
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
-
23
module FileLike #:nodoc:
-
23
attr_writer :original_filename, :content_type
-
-
23
def original_filename
-
@original_filename || "untitled"
-
end
-
-
23
def content_type
-
@content_type || "application/octet-stream"
-
end
-
end
-
-
DEFAULT_ENCODINGS = {
-
"binary" => "base64"
-
23
} unless defined?(DEFAULT_ENCODINGS)
-
-
23
unless defined?(TYPE_NAMES)
-
23
TYPE_NAMES = {
-
"Symbol" => "symbol",
-
"Integer" => "integer",
-
"BigDecimal" => "decimal",
-
"Float" => "float",
-
"TrueClass" => "boolean",
-
"FalseClass" => "boolean",
-
"Date" => "date",
-
"DateTime" => "dateTime",
-
"Time" => "dateTime",
-
"Array" => "array",
-
"Hash" => "hash"
-
}
-
end
-
-
FORMATTING = {
-
"symbol" => Proc.new { |symbol| symbol.to_s },
-
"date" => Proc.new { |date| date.to_s(:db) },
-
"dateTime" => Proc.new { |time| time.xmlschema },
-
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
-
"yaml" => Proc.new { |yaml| yaml.to_yaml }
-
23
} unless defined?(FORMATTING)
-
-
# TODO use regexp instead of Date.parse
-
23
unless defined?(PARSING)
-
23
PARSING = {
-
"symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
-
"date" => Proc.new { |date| ::Date.parse(date) },
-
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
-
"integer" => Proc.new { |integer| integer.to_i },
-
"float" => Proc.new { |float| float.to_f },
-
"decimal" => Proc.new do |number|
-
if String === number
-
number.to_d
-
else
-
BigDecimal(number)
-
end
-
end,
-
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
-
"string" => Proc.new { |string| string.to_s },
-
"yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml },
-
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
-
"binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
-
"file" => Proc.new { |file, entity| _parse_file(file, entity) }
-
}
-
-
23
PARSING.update(
-
"double" => PARSING["float"],
-
"dateTime" => PARSING["datetime"]
-
)
-
end
-
-
23
attr_accessor :depth
-
23
self.depth = 100
-
-
23
delegate :parse, to: :backend
-
-
23
def backend
-
current_thread_backend || @backend
-
end
-
-
23
def backend=(name)
-
23
backend = name && cast_backend_name_to_module(name)
-
23
self.current_thread_backend = backend if current_thread_backend
-
23
@backend = backend
-
end
-
-
23
def with_backend(name)
-
old_backend = current_thread_backend
-
self.current_thread_backend = name && cast_backend_name_to_module(name)
-
yield
-
ensure
-
self.current_thread_backend = old_backend
-
end
-
-
23
def to_tag(key, value, options)
-
type_name = options.delete(:type)
-
merged_options = options.merge(root: key, skip_instruct: true)
-
-
if value.is_a?(::Method) || value.is_a?(::Proc)
-
if value.arity == 1
-
value.call(merged_options)
-
else
-
value.call(merged_options, key.to_s.singularize)
-
end
-
elsif value.respond_to?(:to_xml)
-
value.to_xml(merged_options)
-
else
-
type_name ||= TYPE_NAMES[value.class.name]
-
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
-
type_name = type_name.to_s if type_name
-
type_name = "dateTime" if type_name == "datetime"
-
-
key = rename_key(key.to_s, options)
-
-
attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name }
-
attributes[:nil] = true if value.nil?
-
-
encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name]
-
attributes[:encoding] = encoding if encoding
-
-
formatted_value = FORMATTING[type_name] && !value.nil? ?
-
FORMATTING[type_name].call(value) : value
-
-
options[:builder].tag!(key, formatted_value, attributes)
-
end
-
end
-
-
23
def rename_key(key, options = {})
-
camelize = options[:camelize]
-
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
-
if camelize
-
key = true == camelize ? key.camelize : key.camelize(camelize)
-
end
-
key = _dasherize(key) if dasherize
-
key
-
end
-
-
23
private
-
23
def _dasherize(key)
-
# $2 must be a non-greedy regex for this to work
-
left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3]
-
"#{left}#{middle.tr('_ ', '--')}#{right}"
-
end
-
-
# TODO: Add support for other encodings
-
23
def _parse_binary(bin, entity)
-
case entity["encoding"]
-
when "base64"
-
::Base64.decode64(bin)
-
else
-
bin
-
end
-
end
-
-
23
def _parse_file(file, entity)
-
f = StringIO.new(::Base64.decode64(file))
-
f.extend(FileLike)
-
f.original_filename = entity["name"]
-
f.content_type = entity["content_type"]
-
f
-
end
-
-
23
def current_thread_backend
-
23
Thread.current[:xml_mini_backend]
-
end
-
-
23
def current_thread_backend=(name)
-
Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name)
-
end
-
-
23
def cast_backend_name_to_module(name)
-
23
if name.is_a?(Module)
-
name
-
else
-
23
require "active_support/xml_mini/#{name.downcase}"
-
23
ActiveSupport.const_get("XmlMini_#{name}")
-
end
-
end
-
end
-
-
23
XmlMini.backend = "REXML"
-
end
-
# frozen_string_literal: true
-
-
raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java")
-
-
require "jruby"
-
include Java
-
-
require "active_support/core_ext/object/blank"
-
-
java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder
-
java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory
-
java_import java.io.StringReader unless defined? StringReader
-
java_import org.xml.sax.InputSource unless defined? InputSource
-
java_import org.xml.sax.Attributes unless defined? Attributes
-
java_import org.w3c.dom.Node unless defined? Node
-
-
module ActiveSupport
-
module XmlMini_JDOM #:nodoc:
-
extend self
-
-
CONTENT_KEY = "__content__"
-
-
NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE
-
DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE
-
PROCESSING_INSTRUCTION_NODE TEXT_NODE}
-
-
node_type_map = {}
-
NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type }
-
-
# Parse an XML Document string or IO into a simple hash using Java's jdom.
-
# data::
-
# XML Document string or IO to parse
-
def parse(data)
-
if data.respond_to?(:read)
-
data = data.read
-
end
-
-
if data.blank?
-
{}
-
else
-
@dbf = DocumentBuilderFactory.new_instance
-
# secure processing of java xml
-
# https://archive.is/9xcQQ
-
@dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
-
@dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
-
@dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
-
@dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true)
-
xml_string_reader = StringReader.new(data)
-
xml_input_source = InputSource.new(xml_string_reader)
-
doc = @dbf.new_document_builder.parse(xml_input_source)
-
merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth)
-
end
-
end
-
-
private
-
# Convert an XML element and merge into the hash
-
#
-
# hash::
-
# Hash to merge the converted element into.
-
# element::
-
# XML element to merge into hash
-
def merge_element!(hash, element, depth)
-
raise "Document too deep!" if depth == 0
-
delete_empty(hash)
-
merge!(hash, element.tag_name, collapse(element, depth))
-
end
-
-
def delete_empty(hash)
-
hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == ""
-
end
-
-
# Actually converts an XML document element into a data structure.
-
#
-
# element::
-
# The document element to be collapsed.
-
def collapse(element, depth)
-
hash = get_attributes(element)
-
-
child_nodes = element.child_nodes
-
if child_nodes.length > 0
-
(0...child_nodes.length).each do |i|
-
child = child_nodes.item(i)
-
merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE
-
end
-
merge_texts!(hash, element) unless empty_content?(element)
-
hash
-
else
-
merge_texts!(hash, element)
-
end
-
end
-
-
# Merge all the texts of an element into the hash
-
#
-
# hash::
-
# Hash to add the converted element to.
-
# element::
-
# XML element whose texts are to me merged into the hash
-
def merge_texts!(hash, element)
-
delete_empty(hash)
-
text_children = texts(element)
-
if text_children.join.empty?
-
hash
-
else
-
# must use value to prevent double-escaping
-
merge!(hash, CONTENT_KEY, text_children.join)
-
end
-
end
-
-
# Adds a new key/value pair to an existing Hash. If the key to be added
-
# already exists and the existing value associated with key is not
-
# an Array, it will be wrapped in an Array. Then the new value is
-
# appended to that Array.
-
#
-
# hash::
-
# Hash to add key/value pair to.
-
# key::
-
# Key to be added.
-
# value::
-
# Value to be associated with key.
-
def merge!(hash, key, value)
-
if hash.has_key?(key)
-
if hash[key].instance_of?(Array)
-
hash[key] << value
-
else
-
hash[key] = [hash[key], value]
-
end
-
elsif value.instance_of?(Array)
-
hash[key] = [value]
-
else
-
hash[key] = value
-
end
-
hash
-
end
-
-
# Converts the attributes array of an XML element into a hash.
-
# Returns an empty Hash if node has no attributes.
-
#
-
# element::
-
# XML element to extract attributes from.
-
def get_attributes(element)
-
attribute_hash = {}
-
attributes = element.attributes
-
(0...attributes.length).each do |i|
-
attribute_hash[CONTENT_KEY] ||= ""
-
attribute_hash[attributes.item(i).name] = attributes.item(i).value
-
end
-
attribute_hash
-
end
-
-
# Determines if a document element has text content
-
#
-
# element::
-
# XML element to be checked.
-
def texts(element)
-
texts = []
-
child_nodes = element.child_nodes
-
(0...child_nodes.length).each do |i|
-
item = child_nodes.item(i)
-
if item.node_type == Node.TEXT_NODE
-
texts << item.get_data
-
end
-
end
-
texts
-
end
-
-
# Determines if a document element has text content
-
#
-
# element::
-
# XML element to be checked.
-
def empty_content?(element)
-
text = +""
-
child_nodes = element.child_nodes
-
(0...child_nodes.length).each do |i|
-
item = child_nodes.item(i)
-
if item.node_type == Node.TEXT_NODE
-
text << item.get_data.strip
-
end
-
end
-
text.strip.length == 0
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "libxml"
-
require "active_support/core_ext/object/blank"
-
require "stringio"
-
-
module ActiveSupport
-
module XmlMini_LibXML #:nodoc:
-
extend self
-
-
# Parse an XML Document string or IO into a simple hash using libxml.
-
# data::
-
# XML Document string or IO to parse
-
def parse(data)
-
if !data.respond_to?(:read)
-
data = StringIO.new(data || "")
-
end
-
-
if data.eof?
-
{}
-
else
-
LibXML::XML::Parser.io(data).parse.to_hash
-
end
-
end
-
end
-
end
-
-
module LibXML #:nodoc:
-
module Conversions #:nodoc:
-
module Document #:nodoc:
-
def to_hash
-
root.to_hash
-
end
-
end
-
-
module Node #:nodoc:
-
CONTENT_ROOT = "__content__"
-
-
# Convert XML document to hash.
-
#
-
# hash::
-
# Hash to merge the converted element into.
-
def to_hash(hash = {})
-
node_hash = {}
-
-
# Insert node hash into parent hash correctly.
-
case hash[name]
-
when Array then hash[name] << node_hash
-
when Hash then hash[name] = [hash[name], node_hash]
-
when nil then hash[name] = node_hash
-
end
-
-
# Handle child elements
-
each_child do |c|
-
if c.element?
-
c.to_hash(node_hash)
-
elsif c.text? || c.cdata?
-
node_hash[CONTENT_ROOT] ||= +""
-
node_hash[CONTENT_ROOT] << c.content
-
end
-
end
-
-
# Remove content node if it is blank
-
if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank?
-
node_hash.delete(CONTENT_ROOT)
-
end
-
-
# Handle attributes
-
each_attr { |a| node_hash[a.name] = a.value }
-
-
hash
-
end
-
end
-
end
-
end
-
-
# :enddoc:
-
-
LibXML::XML::Document.include(LibXML::Conversions::Document)
-
LibXML::XML::Node.include(LibXML::Conversions::Node)
-
# frozen_string_literal: true
-
-
require "libxml"
-
require "active_support/core_ext/object/blank"
-
require "stringio"
-
-
module ActiveSupport
-
module XmlMini_LibXMLSAX #:nodoc:
-
extend self
-
-
# Class that will build the hash while the XML document
-
# is being parsed using SAX events.
-
class HashBuilder
-
include LibXML::XML::SaxParser::Callbacks
-
-
CONTENT_KEY = "__content__"
-
HASH_SIZE_KEY = "__hash_size__"
-
-
attr_reader :hash
-
-
def current_hash
-
@hash_stack.last
-
end
-
-
def on_start_document
-
@hash = { CONTENT_KEY => +"" }
-
@hash_stack = [@hash]
-
end
-
-
def on_end_document
-
@hash = @hash_stack.pop
-
@hash.delete(CONTENT_KEY)
-
end
-
-
def on_start_element(name, attrs = {})
-
new_hash = { CONTENT_KEY => +"" }.merge!(attrs)
-
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
-
-
case current_hash[name]
-
when Array then current_hash[name] << new_hash
-
when Hash then current_hash[name] = [current_hash[name], new_hash]
-
when nil then current_hash[name] = new_hash
-
end
-
-
@hash_stack.push(new_hash)
-
end
-
-
def on_end_element(name)
-
if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ""
-
current_hash.delete(CONTENT_KEY)
-
end
-
@hash_stack.pop
-
end
-
-
def on_characters(string)
-
current_hash[CONTENT_KEY] << string
-
end
-
-
alias_method :on_cdata_block, :on_characters
-
end
-
-
attr_accessor :document_class
-
self.document_class = HashBuilder
-
-
def parse(data)
-
if !data.respond_to?(:read)
-
data = StringIO.new(data || "")
-
end
-
-
if data.eof?
-
{}
-
else
-
LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
-
parser = LibXML::XML::SaxParser.io(data)
-
document = document_class.new
-
-
parser.callbacks = document
-
parser.parse
-
document.hash
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
begin
-
require "nokogiri"
-
rescue LoadError => e
-
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
require "active_support/core_ext/object/blank"
-
require "stringio"
-
-
module ActiveSupport
-
module XmlMini_Nokogiri #:nodoc:
-
extend self
-
-
# Parse an XML Document string or IO into a simple hash using libxml / nokogiri.
-
# data::
-
# XML Document string or IO to parse
-
def parse(data)
-
if !data.respond_to?(:read)
-
data = StringIO.new(data || "")
-
end
-
-
if data.eof?
-
{}
-
else
-
doc = Nokogiri::XML(data)
-
raise doc.errors.first if doc.errors.length > 0
-
doc.to_hash
-
end
-
end
-
-
module Conversions #:nodoc:
-
module Document #:nodoc:
-
def to_hash
-
root.to_hash
-
end
-
end
-
-
module Node #:nodoc:
-
CONTENT_ROOT = "__content__"
-
-
# Convert XML document to hash.
-
#
-
# hash::
-
# Hash to merge the converted element into.
-
def to_hash(hash = {})
-
node_hash = {}
-
-
# Insert node hash into parent hash correctly.
-
case hash[name]
-
when Array then hash[name] << node_hash
-
when Hash then hash[name] = [hash[name], node_hash]
-
when nil then hash[name] = node_hash
-
end
-
-
# Handle child elements
-
children.each do |c|
-
if c.element?
-
c.to_hash(node_hash)
-
elsif c.text? || c.cdata?
-
node_hash[CONTENT_ROOT] ||= +""
-
node_hash[CONTENT_ROOT] << c.content
-
end
-
end
-
-
# Remove content node if it is blank and there are child tags
-
if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank?
-
node_hash.delete(CONTENT_ROOT)
-
end
-
-
# Handle attributes
-
attribute_nodes.each { |a| node_hash[a.node_name] = a.value }
-
-
hash
-
end
-
end
-
end
-
-
Nokogiri::XML::Document.include(Conversions::Document)
-
Nokogiri::XML::Node.include(Conversions::Node)
-
end
-
end
-
# frozen_string_literal: true
-
-
begin
-
require "nokogiri"
-
rescue LoadError => e
-
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
-
raise e
-
end
-
require "active_support/core_ext/object/blank"
-
require "stringio"
-
-
module ActiveSupport
-
module XmlMini_NokogiriSAX #:nodoc:
-
extend self
-
-
# Class that will build the hash while the XML document
-
# is being parsed using SAX events.
-
class HashBuilder < Nokogiri::XML::SAX::Document
-
CONTENT_KEY = "__content__"
-
HASH_SIZE_KEY = "__hash_size__"
-
-
attr_reader :hash
-
-
def current_hash
-
@hash_stack.last
-
end
-
-
def start_document
-
@hash = {}
-
@hash_stack = [@hash]
-
end
-
-
def end_document
-
raise "Parse stack not empty!" if @hash_stack.size > 1
-
end
-
-
def error(error_message)
-
raise error_message
-
end
-
-
def start_element(name, attrs = [])
-
new_hash = { CONTENT_KEY => +"" }.merge!(Hash[attrs])
-
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
-
-
case current_hash[name]
-
when Array then current_hash[name] << new_hash
-
when Hash then current_hash[name] = [current_hash[name], new_hash]
-
when nil then current_hash[name] = new_hash
-
end
-
-
@hash_stack.push(new_hash)
-
end
-
-
def end_element(name)
-
if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ""
-
current_hash.delete(CONTENT_KEY)
-
end
-
@hash_stack.pop
-
end
-
-
def characters(string)
-
current_hash[CONTENT_KEY] << string
-
end
-
-
alias_method :cdata_block, :characters
-
end
-
-
attr_accessor :document_class
-
self.document_class = HashBuilder
-
-
def parse(data)
-
if !data.respond_to?(:read)
-
data = StringIO.new(data || "")
-
end
-
-
if data.eof?
-
{}
-
else
-
document = document_class.new
-
parser = Nokogiri::XML::SAX::Parser.new(document)
-
parser.parse(data)
-
document.hash
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
23
require "active_support/core_ext/kernel/reporting"
-
23
require "active_support/core_ext/object/blank"
-
23
require "stringio"
-
-
23
module ActiveSupport
-
23
module XmlMini_REXML #:nodoc:
-
23
extend self
-
-
23
CONTENT_KEY = "__content__"
-
-
# Parse an XML Document string or IO into a simple hash.
-
#
-
# Same as XmlSimple::xml_in but doesn't shoot itself in the foot,
-
# and uses the defaults from Active Support.
-
#
-
# data::
-
# XML Document string or IO to parse
-
23
def parse(data)
-
if !data.respond_to?(:read)
-
data = StringIO.new(data || "")
-
end
-
-
if data.eof?
-
{}
-
else
-
silence_warnings { require "rexml/document" } unless defined?(REXML::Document)
-
doc = REXML::Document.new(data)
-
-
if doc.root
-
merge_element!({}, doc.root, XmlMini.depth)
-
else
-
raise REXML::ParseException,
-
"The document #{doc.to_s.inspect} does not have a valid root"
-
end
-
end
-
end
-
-
23
private
-
# Convert an XML element and merge into the hash
-
#
-
# hash::
-
# Hash to merge the converted element into.
-
# element::
-
# XML element to merge into hash
-
23
def merge_element!(hash, element, depth)
-
raise REXML::ParseException, "The document is too deep" if depth == 0
-
merge!(hash, element.name, collapse(element, depth))
-
end
-
-
# Actually converts an XML document element into a data structure.
-
#
-
# element::
-
# The document element to be collapsed.
-
23
def collapse(element, depth)
-
hash = get_attributes(element)
-
-
if element.has_elements?
-
element.each_element { |child| merge_element!(hash, child, depth - 1) }
-
merge_texts!(hash, element) unless empty_content?(element)
-
hash
-
else
-
merge_texts!(hash, element)
-
end
-
end
-
-
# Merge all the texts of an element into the hash
-
#
-
# hash::
-
# Hash to add the converted element to.
-
# element::
-
# XML element whose texts are to me merged into the hash
-
23
def merge_texts!(hash, element)
-
unless element.has_text?
-
hash
-
else
-
# must use value to prevent double-escaping
-
texts = +""
-
element.texts.each { |t| texts << t.value }
-
merge!(hash, CONTENT_KEY, texts)
-
end
-
end
-
-
# Adds a new key/value pair to an existing Hash. If the key to be added
-
# already exists and the existing value associated with key is not
-
# an Array, it will be wrapped in an Array. Then the new value is
-
# appended to that Array.
-
#
-
# hash::
-
# Hash to add key/value pair to.
-
# key::
-
# Key to be added.
-
# value::
-
# Value to be associated with key.
-
23
def merge!(hash, key, value)
-
if hash.has_key?(key)
-
if hash[key].instance_of?(Array)
-
hash[key] << value
-
else
-
hash[key] = [hash[key], value]
-
end
-
elsif value.instance_of?(Array)
-
hash[key] = [value]
-
else
-
hash[key] = value
-
end
-
hash
-
end
-
-
# Converts the attributes array of an XML element into a hash.
-
# Returns an empty Hash if node has no attributes.
-
#
-
# element::
-
# XML element to extract attributes from.
-
23
def get_attributes(element)
-
attributes = {}
-
element.attributes.each { |n, v| attributes[n] = v }
-
attributes
-
end
-
-
# Determines if a document element has text content
-
#
-
# element::
-
# XML element to be checked.
-
23
def empty_content?(element)
-
element.texts.join.blank?
-
end
-
end
-
end